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.

Tuesday, September 8, 2009

Function Discovery Intro

The other day I wrote a post about using SetupDi to enumerate PnP devices.

SetupDi Post

But SetupDi is not the only API to enumerate devices; Function Discovery can also enumerate PnP devices along with a host of other capabilities.

Function Discovery (FD) came to life as part of Windows Vista. FD’s main goal was to provide a unified API and interfaces for gathering functionality, properties, and notifications from various providers. PnP just happens to be one of the providers. Before FD, different API sets were required for discovering functionality of devices; for example you could use SetupDiGetClassDevs to find physically connected devices, but you had to use other APIs for network devices or printers. Using FD, you can use the same set of interfaces and methods for PnP and any number of devices exposed trough a provider. Vista shipped with in-box-providers for PnP, PnP-X (WSD & SSDP), Registry, NetBIOS, and the capability for third parties to create their own providers, and Windows 7 there are even more providers.

If you have the Windows SDK installed (I assume that you would if you are interested in writing this kind of code), you can do some header spelunking. Check out FunctionDiscoveryCategories.h to get an idea of what providers you can try to use. Also you can dig into the registry to see what other providers are registered on the system at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Function Discovery\Categories.

So if you need to enumerate devices and/or get notifications, FD can help assuming there is a provider for it.

To start out with, I wrote a sample FD code using the PnP provider to enumerate PnP devices just like I did in the SetupDiGetClassDevs example from before. I wrote this a while back now, so I hope there are no bug in this code.

I am having it print out a few properties from each function instance. A good place to go to find what kinds of properties are discoverable through FD is the header files included in the SDK: functiondiscoverykeys.h, and functiondiscoverykeys_devpkey.h. Not all PKEYs are populated by all providers; for example, PKEY_WSD_MetadataVersion will not be populated by the PnP provider, but will be populated by the WSD provider. Generally PKEYs populated by the PnP are prefixed with PKEY_Device_, and the properties you can get with SetupDi are available with the PnP FD provider.

For a more in depth reference to Function Discovery, refer to the official MSDN FD documentation.


/* displays pnp function instances */

#include <stdio.h>
#include <FunctionDiscovery.h>

HRESULT PrintFIs(IFunctionInstanceCollection* FIs);

int __cdecl wmain(
__in int argc,
__in_ecount(argc) PWSTR
)
{
HRESULT hr = S_OK;
IFunctionDiscovery *pFD = NULL;
IFunctionInstanceCollectionQuery *pPnpQuery = NULL;
IFunctionInstanceCollection *pFICollection = NULL;

hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);

// CoCreate FunctionDiscovery
if (S_OK == hr)
{
hr = CoCreateInstance(
__uuidof(FunctionDiscovery),
NULL,
CLSCTX_ALL,
__uuidof(IFunctionDiscovery),
(PVOID*)&pFD);
}

// Query the pnp provider
if (S_OK == hr)
{
hr = pFD->CreateInstanceCollectionQuery(
FCTN_CATEGORY_PNP, // pnp category (defined in functiondiscoverycategories.h)
NULL, // subcategory
FALSE, // include subcategories
NULL, // notification callback
NULL, // context
&pPnpQuery); // FI collection query
}

/*
// * optional *
// add property constraints
// generally only works with core FD properties, and not provider specific properties
if (S_OK == hr)
{
PROPVARIANT pv;

PropVariantClear(&pv);

pv.vt = VT_UINT;
pv.uintVal = 0;

hr = pPnpQuery->AddPropertyConstraint(PKEY_FD_Visibility, &pv, QC_EQUALS);

PropVariantClear(&pv);
}
*/

/*
// * optional *
// add query constraints
// refer to functiondiscoveryconstraints.h
if (S_OK == hr)
{
hr = pPnpQuery->AddQueryConstraint(PNP_CONSTRAINTVALUE_NOTPRESENT, FD_CONSTRAINTVALUE_TRUE);
}
*/

if (S_OK == hr)
{
hr = pPnpQuery->Execute(&pFICollection);
}

if (S_OK == hr)
{
hr = PrintFIs(pFICollection);
}

// clean up
if (pFD)
{
pFD->Release();
}

if (pPnpQuery)
{
pPnpQuery->Release();
}

if (pFICollection)
{
pFICollection->Release();
}

CoUninitialize();

if (S_OK != hr)
{
wprintf(L"an error occured (hr == 0x%x)\n", hr);
return 1;
}

return 0;
}

HRESULT PrintFIs(
IFunctionInstanceCollection* FIs
)
{
HRESULT hr = S_OK;
DWORD cFIs = 0;
IFunctionInstance *pFI = NULL;
IFunctionInstanceCollection *pDeviceFunctionCollection = NULL;

if (FIs)
{
hr = FIs->GetCount(&cFIs);

wprintf(L"*******************************************************\n");
wprintf(L"* %i Function Instances\n", cFIs);
wprintf(L"*******************************************************\n\n");

// go through each function instance
for (DWORD i = 0; S_OK == hr && i < cFIs; i++)
{
hr = FIs->Item(i, &pFI);

if (S_OK == hr)
{
IPropertyStore *pPropertyStore = NULL;

pFI->OpenPropertyStore(STGM_READ, &pPropertyStore);

if (pPropertyStore)
{
PROPVARIANT pv;

PropVariantClear(&pv);

// PKEYs can be found in these headers in the SDK:
// functiondiscoverykeys.h functiondiscoverykeys_devpkey.h
// Providers do not populate all PKEYs.
hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);

if (S_OK == hr)
{
wprintf(L"Device Friendly Name : \"%s\"\n", (pv.vt == VT_LPWSTR) ? pv.pwszVal : L"");
}

PropVariantClear(&pv);


hr = pPropertyStore->GetValue(PKEY_Device_InstanceId, &pv);

if (S_OK == hr && VT_LPWSTR == pv.vt)
{
wprintf(L"\tDevice Instance ID : \"%s\"\n", pv.pwszVal);
}

PropVariantClear(&pv);

hr = pPropertyStore->GetValue(PKEY_Device_Class, &pv);

if (S_OK == hr && VT_LPWSTR == pv.vt)
{
wprintf(L"\tClass : %s",pv.pwszVal);
}

PropVariantClear(&pv);

hr = pPropertyStore->GetValue(PKEY_Device_ClassGuid, &pv);

if (S_OK == hr && VT_CLSID == pv.vt)
{
wprintf(L"\t(GUID : %x-%x-%x-%x%x-%x%x%x%x%x%x)\n",
pv.puuid->Data1,
pv.puuid->Data2,
pv.puuid->Data3,
pv.puuid->Data4[0],
pv.puuid->Data4[1],
pv.puuid->Data4[2],
pv.puuid->Data4[3],
pv.puuid->Data4[4],
pv.puuid->Data4[5],
pv.puuid->Data4[6],
pv.puuid->Data4[7]
);
}

PropVariantClear(&pv);

pPropertyStore->Release();
}
}

if (pFI)
{
pFI->Release();
pFI = NULL;
}

if (pDeviceFunctionCollection)
{
pDeviceFunctionCollection->Release();
pDeviceFunctionCollection = NULL;
}
}
}

return hr;
}