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;
}

No comments:

Post a Comment