This is the part 3 of the Exploring Delphi XE3 – WinApi Additions – Winapi.Functiondiscovery Article.
The Function Discovery API can be used not just for enumerate devices also you can receive notifications as well, like when a device is added, removed or a property of the device is modified. In order to receive such notifications you must implement the IFunctionDiscoveryNotification interface and pass a instance of this implementation to the CreateInstanceCollectionQuery method, then you must restrict the result of the query with the method AddQueryConstraint passing the PROVIDERPNP_QUERYCONSTRAINT_NOTIFICATIONSONLY value to only receive notifications and finally call the IFunctionInstanceCollectionQuery.Execute method.
Implementing the IFunctionDiscoveryNotification interface
The IFunctionDiscoveryNotification interface exposes 3 methods to receive the results of the asynchronous queries returned by the execution of the IFunctionInstanceCollectionQuery.Execute method.
| OnError | Receives errors that occur during asynchronous query processing. |
| OnEvent | Receives any add, remove, or update events. |
| OnUpdate | Indicates that a function instance has been added, removed, or changed. |
This is the Delphi declaration of the IFunctionDiscoveryNotification interface.
IFunctionDiscoveryNotification = interface(IUnknown) [SID_IFunctionDiscoveryNotification] function OnUpdate(enumQueryUpdateAction: QueryUpdateAction; fdqcQueryContext: FDQUERYCONTEXT; pIFunctionInstance: IFunctionInstance): HRESULT; stdcall; function OnError(hr: HRESULT; fdqcQueryContext: FDQUERYCONTEXT; pszProvider: PWCHAR): HRESULT; stdcall; function OnEvent(dwEventID: DWORD; fdqcQueryContext: FDQUERYCONTEXT; pszProvider: PWCHAR): HRESULT; stdcall; end;
Now check this Delphi implementation for the IFunctionDiscoveryNotification interface.
type
TFunctionDiscoveryOnUpdate = procedure(enumQueryUpdateAction: QueryUpdateAction; fdqcQueryContext: FDQUERYCONTEXT;
pIFunctionInstance: IFunctionInstance) of object;
TFunctionDiscoveryOnError = procedure(hr: HRESULT; fdqcQueryContext: FDQUERYCONTEXT; pszProvider: PWCHAR) of object;
TFunctionDiscoveryOnEvent = procedure(dwEventID: DWORD; fdqcQueryContext: FDQUERYCONTEXT; pszProvider: PWCHAR) of object;
TFunctionDiscoveryNotificationSync=class(TInterfacedObject, IFunctionDiscoveryNotification)
private
FAction : QueryUpdateAction;
FEventAdd : TEvent;
FEventRemove : TEvent;
FEventChange : TEvent;
FOnUpdateEvent : TFunctionDiscoveryOnUpdate;
FOnErrorEvent : TFunctionDiscoveryOnError;
FOnEventEvent : TFunctionDiscoveryOnEvent;
function OnUpdate(enumQueryUpdateAction: QueryUpdateAction; fdqcQueryContext: FDQUERYCONTEXT;
pIFunctionInstance: IFunctionInstance): HRESULT; stdcall;
function OnError(hr: HRESULT; fdqcQueryContext: FDQUERYCONTEXT; pszProvider: PWCHAR): HRESULT; stdcall;
function OnEvent(dwEventID: DWORD; fdqcQueryContext: FDQUERYCONTEXT; pszProvider: PWCHAR): HRESULT; stdcall;
public
constructor Create;
destructor Destroy; override;
function WaitFor(dwTimeout : DWORD; pszCategory: PWCHAR; eAction : QueryUpdateAction) : HRESULT;
property OnUpdateEvent: TFunctionDiscoveryOnUpdate read FOnUpdateEvent write FOnUpdateEvent;
property OnErrorEvent : TFunctionDiscoveryOnError read FOnErrorEvent write FOnErrorEvent;
property OnEventEvent : TFunctionDiscoveryOnEvent read FOnEventEvent write FOnEventEvent;
end;
{TFunctionDiscoveryNotificationSync}
constructor TFunctionDiscoveryNotificationSync.Create;
begin
inherited;
FOnUpdateEvent:=nil;
//create the events objects
FEventAdd := TEvent.Create(nil, False, False, '', true);
FEventRemove := TEvent.Create(nil, False, False, '', true);
FEventChange := TEvent.Create(nil, False, False, '', true);
end;
destructor TFunctionDiscoveryNotificationSync.Destroy;
begin
//release the event objects
FEventAdd.Free;
FEventRemove.Free;
FEventChange.Free;
inherited;
end;
function TFunctionDiscoveryNotificationSync.OnError(hr: HRESULT;
fdqcQueryContext: FDQUERYCONTEXT; pszProvider: PWCHAR): HRESULT;
begin
//send the error notification if a callback method was defined
if @FOnErrorEvent<>nil then
FOnErrorEvent(hr, fdqcQueryContext, pszProvider);
Exit(S_OK);
end;
function TFunctionDiscoveryNotificationSync.OnEvent(dwEventID: DWORD;
fdqcQueryContext: FDQUERYCONTEXT; pszProvider: PWCHAR): HRESULT;
begin
//send the OnEvent notification if a callback method was defined
if @FOnEventEvent<>nil then
FOnEventEvent(dwEventID, fdqcQueryContext, pszProvider);
Exit(S_OK);
end;
function TFunctionDiscoveryNotificationSync.OnUpdate(
enumQueryUpdateAction: QueryUpdateAction; fdqcQueryContext: FDQUERYCONTEXT;
pIFunctionInstance: IFunctionInstance): HRESULT;
begin
//signal the event object
case enumQueryUpdateAction of
QUA_ADD : FEventAdd.SetEvent;
QUA_REMOVE : FEventRemove.SetEvent;
QUA_CHANGE : FEventChange.SetEvent;
end;
//send the OnEvent notification if a callback method was defined
if (@FOnUpdateEvent<>nil) and (FAction=enumQueryUpdateAction) then
FOnUpdateEvent(enumQueryUpdateAction, fdqcQueryContext, pIFunctionInstance);
Exit(S_OK);
end;
function TFunctionDiscoveryNotificationSync.WaitFor(dwTimeout : DWORD; pszCategory: PWCHAR; eAction : QueryUpdateAction) : HRESULT;
var
hr : HRESULT;
LEvent : TEvent;
LWaitResult : TWaitResult;
FFunctionDiscovery : IFunctionDiscovery;
ppIFunctionInstanceCollection: IFunctionInstanceCollection;
ppIFunctionInstanceCollectionQuery: IFunctionInstanceCollectionQuery;
begin
FAction:=eAction;
//reset the event objects
FEventAdd.ResetEvent;
FEventRemove.ResetEvent;
FEventChange.ResetEvent;
//create a instance to the IFunctionDiscovery
FFunctionDiscovery := CreateComObject(CLSID_FunctionDiscovery) as IFunctionDiscovery;
//create a new query passing the current class as callback
hr := FFunctionDiscovery.CreateInstanceCollectionQuery(FCTN_CATEGORY_PNP, nil, true, Self, nil, ppIFunctionInstanceCollectionQuery);
//instruct to the query to only receive notifications
if hr=S_OK then
hr := ppIFunctionInstanceCollectionQuery.AddQueryConstraint(PROVIDERPNP_QUERYCONSTRAINT_NOTIFICATIONSONLY,'TRUE');
//execute the query
if hr=S_OK then
hr := ppIFunctionInstanceCollectionQuery.Execute(ppIFunctionInstanceCollection);
if( hr=E_PENDING) then hr := S_OK;
case eAction of
QUA_ADD : LEvent:=FEventAdd;
QUA_REMOVE : LEvent:=FEventRemove;
QUA_CHANGE : LEvent:=FEventChange;
else
LEvent := nil;
end;
if (hr=S_OK) and (LEvent<>nil) then
LWaitResult:= LEvent.WaitFor(dwTimeout);
// One device may correspond to multiple function instances
// This sleep allows the OnUpdate call to output information
// about each Function Instance.
// THIS SLEEP IS MERELY FOR DISPLAY PURPOSES
Sleep(1000);
Exit(hr);
end;
Demo Application
Now using the above implementation we can receive notification about the devices, you can test the next sample app inserting a USB device and then removing.
type
TNotifier=class
procedure OnUpdate(enumQueryUpdateAction: QueryUpdateAction; fdqcQueryContext: FDQUERYCONTEXT;
pIFunctionInstance: IFunctionInstance);
end;
procedure NotificationDemo;
Const
Timeout = 20000;
var
hr : HResult;
pIFunctionDiscoveryNotification : TFunctionDiscoveryNotificationSync;
LNotifier : TNotifier;
begin
LNotifier:=TNotifier.Create;
try
pIFunctionDiscoveryNotification:=TFunctionDiscoveryNotificationSync.Create;
try
//set the callback
pIFunctionDiscoveryNotification.OnUpdateEvent:=LNotifier.OnUpdate;
Writeln(Format('Waiting for %d ms, to plug in a PnP device',[Timeout]));
pIFunctionDiscoveryNotification.WaitFor(Timeout, FCTN_CATEGORY_PNP, QUA_ADD);
Writeln('Done');
finally
pIFunctionDiscoveryNotification:=nil;
end;
pIFunctionDiscoveryNotification:=TFunctionDiscoveryNotificationSync.Create;
try
//set the callback
pIFunctionDiscoveryNotification.OnUpdateEvent:=LNotifier.OnUpdate;
Writeln(Format('Waiting for %d ms, to remove a PnP device',[Timeout]));
pIFunctionDiscoveryNotification.WaitFor(Timeout, FCTN_CATEGORY_PNP, QUA_REMOVE);
Writeln('Done');
finally
pIFunctionDiscoveryNotification:=nil;
end;
finally
LNotifier.Free;
end;
end;
{ TNotifier }
procedure TNotifier.OnUpdate(enumQueryUpdateAction: QueryUpdateAction;
fdqcQueryContext: FDQUERYCONTEXT; pIFunctionInstance: IFunctionInstance);
var
ppIPropertyStore : IPropertyStore;
pv : TPropVariant;
begin
case enumQueryUpdateAction of
QUA_ADD : Writeln(Format('Action : %s',['Add']));
QUA_REMOVE : Writeln(Format('Action : %s',['Remove']));
QUA_CHANGE : Writeln(Format('Action : %s',['Change']));
end;
if Succeeded(pIFunctionInstance.OpenPropertyStore(STGM_READ, ppIPropertyStore)) then
if Succeeded(ppIPropertyStore.GetValue(PKEY_Device_DeviceDesc, pv)) then
Writeln(Format('Device Desc. %s',[pv.pwszVal]));
if Succeeded(ppIPropertyStore.GetValue(PKEY_Device_Class, pv)) then
Writeln(Format('Class %s',[pv.pwszVal]));
if Succeeded(ppIPropertyStore.GetValue(PKEY_Device_Manufacturer, pv)) then
Writeln(Format('Manufacturer %s',[pv.pwszVal]));
end;
begin
try
ReportMemoryLeaksOnShutdown:=True;
if (Win32MajorVersion >= 6) then // available on Vista (or later)
begin
if Succeeded(CoInitializeEx(nil, COINIT_MULTITHREADED)) then
try
NotificationDemo;
finally
CoUninitialize;
end;
end
else
Writeln('Windows version not compatible');
except
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
September 13, 2012 at 10:01 pm
Is there any way through the Function Discovery API to restrict/reject a USB add?
September 14, 2012 at 12:02 pm
AFAIK, there is not way to prevent the add of a USB device using the Function Discovery API, you best toption is detect insertion of the device and then remove the device using the SetupDI API.
February 11, 2013 at 9:15 pm
Are there any examples for how to use this API to continually be notify all events types, rather than just wait for a few specific ones?