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?