Tuesday, September 15, 2009

Function Discovery: Callback Objects, Implementing IFunctionDiscoveryNotification

If you missed my introductory post on Function Discovery (FD), you might want to go back there and give it a once over. It will give you a quick primer on what FD is about.

Function Discovery Intro

In my first FD post I provided a sample using the PnP FD provider to enumerate present devices. The FD PnP provider probably the most used provider and is easier to use than SetupDiGetGlassDevs especially if your program is already using COM. Unfortunately my first sample didn’t include a callback object which is required to get notifications from the provider. It gets worse than that; the PnP provider is actually the only provider (except the registry provider) that will provide synchronous results (IFunctionInstanceCollection)when you execute your query. In other words, every other inbox FD provider is asynchronous, and you won’t get any function instance (FI) results unless you provide a callback object and get them asynchronously.

Don’t worry; writing callback objects is easy, and I will show you how with an example. You start off creating a query just like we did in the first example, except we will have to change two parts. First you will need to create your callback object, and then pass it as a parameter to the CreateInstanceCollectionQuery method call. Finally when you execute the query, you will not get a function instance query back unless it is the PnP or registry provider, and the call will return E_PENDING. E_PENDING is not an error if you are using an asynchronous provider; it just means that the provider will give you function instances asynchronously to your call back object. If the provider is async and returns E_PENDING, it should also send FD_EVENTID_SEARCHCOMPLETE to the callback’s OnEvent method.

Here is a simple sample code for a callback object.


class CNotificationCallback : public IFunctionDiscoveryNotification
{
public:

STDMETHODIMP_(ULONG) AddRef();

STDMETHODIMP_(ULONG) Release();

STDMETHODIMP QueryInterface(
REFIID riid,
__deref_out_opt void **ppv);

STDMETHODIMP OnUpdate(
QueryUpdateAction enumQueryUpdateAction,
FDQUERYCONTEXT fdqcQueryContext,
__in IFunctionInstance *pIFunctionInstance);

STDMETHODIMP OnError(
HRESULT hr,
FDQUERYCONTEXT fdqcQueryContext,
PCWSTR pszProvider);

STDMETHODIMP OnEvent(
DWORD dwEventID,
FDQUERYCONTEXT fdqcQueryContext,
PCWSTR pszProvider);

CNotificationCallback();

protected:
LONG m_cRef;
};

CNotificationCallback::CNotificationCallback():
m_cRef(1)
{
}

STDMETHODIMP_(ULONG) CNotificationCallback::AddRef()
{
return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CNotificationCallback::Release()
{
LONG cRef = InterlockedDecrement(&m_cRef);
if (0 == cRef)
{
delete this;
}

return cRef;
}

STDMETHODIMP CNotificationCallback::QueryInterface(
REFIID riid,
__deref_out_opt void **ppv)
{
HRESULT hr = S_OK;

if (ppv)
{
*ppv = NULL;
}
else
{
hr = E_INVALIDARG;
}

if (S_OK == hr)
{
if (__uuidof(IUnknown) == riid )
{
AddRef();
*ppv = (IUnknown*) this;
}
else if (__uuidof(IFunctionDiscoveryNotification) == riid)
{
AddRef();
*ppv = (IFunctionDiscoveryNotification*) this;
}
else
{
hr = E_NOINTERFACE;
}
}

return hr;
}

STDMETHODIMP CNotificationCallback::OnUpdate(
QueryUpdateAction enumQueryUpdateAction,
FDQUERYCONTEXT fdqcQueryContext,
__in IFunctionInstance* pIFunctionInstance)
{
HRESULT hr = S_OK;

switch (enumQueryUpdateAction)
{
case QUA_ADD:
wprintf(L"QUA_ADD\n");
break;
case QUA_REMOVE:
wprintf(L"QUA_REMOVE\n");
break;
case QUA_CHANGE:
wprintf(L"QUA_CHANGE\n");
break;
}

return S_OK;
}

STDMETHODIMP CNotificationCallback::OnError(
HRESULT hr,
FDQUERYCONTEXT fdqcQueryContext,
PCWSTR pszProvider)
{
wprintf(L"****** ERROR: 0x%08x\n", hr);

return S_OK;
}

STDMETHODIMP CNotificationCallback::OnEvent(
DWORD dwEventID,
FDQUERYCONTEXT fdqcQueryContext,
PCWSTR pszProvider)
{
wprintf(L"Event: %d\n", dwEventID);

return S_OK;
}

This is the most basic example of how you might write a callback. Let’s pretend that we wanted to take an async provider like WSD and make it synchronized. One way you could do this is by passing in an empty function instance collection, and a handle that the callback object can signal once it receives FD_EVENTID_SEARCHCOMPLETE from the provider. In the main thread you could just wait on the handle. Often the callback interface is inherited in a bigger fancier class that does a lot more things than implement IFunctionDiscoveryNotification; the sky is the limit on how you want to structure your code here. Just make sure you exercise good tread safety. If you are sharing memory between the main program thread and your callback’s tread, be sure to use a SRW lock.

Now armed with callbacks, you can use two of FD’s main features: enumerating, and receiving notifications. With callbacks you will be able to take advantage of all of the providers on your computer.

Hopefully next time we can see how simple it is to write a FD provider and register it on your computer. Once we can write a simple provider, we can move on to writing full blown PnP-X providers. If you want to skip straight to PnP-X, there is a sample of one in the Windows SDK already, but hopefully I will be able to break it down into more digestible chunks. :)

If you are interested in using FD and want some extra help, email me and I can get you going on writing your provider or whatever you want to get accomplished.

4 comments:

  1. Dear Sam,

    I'm looking for your famous GPU FDTD code !

    Where can i find this little diamond ?

    Thanks in advance

    A little french student ...

    ReplyDelete
  2. Which version are you looking for? I had a version I wrote using Cg and frame buffer objects that performed very well, but kind of useless since it did not have PML, and I was working on a CUDA version. I never got that one to run as well and my Cg version, but it has the potential to have a PML... I no longer work for the Air Force Research Lab, so I haven't been working on GPU suff lately. Maybe I should just turn it into a blog post.

    ReplyDelete
  3. "One way you could do this is by passing in an empty function instance collection, and a handle that the callback object can signal once it receives FD_EVENTID_SEARCHCOMPLETE from the provider. In the main thread you could just wait on the handle".

    First of all, thanks for the wonderful articles. I am trying to perform a WSD Discovery. Can you please let me know how to do the above mentioned part. Is there any samples available.

    I have created an instance of NotifyCallback class as instructed and passed in as parameter to CreateInstanceCollectionQuery. Please help me in getting the results in the callback.

    ReplyDelete
    Replies
    1. @Effjay, you should checkout the Windows.Devices.Enumeration API. It now exposes network devices like WSD, UPnP, DNS-SD, etc. along with wireless deivces like Bluetooth, BTLE, WFD, etc. Function Discovery has been on life support for over 8 years now.

      Here is the sample. https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/DeviceEnumerationAndPairing

      Delete