Tuesday, October 13, 2009

Making Your Function Discovery (FD) Provider Run In Proc

I was looking through my basic FD provider sample that I wrote a few months ago and realized that I can't directly publish it on the Internet. I don't want to provide a sample that doesn't do anything real, so I will need to rewrite a new provider sample that publishable and is realistic. In the mean time, if you want to push ahead and try writing your own provider, it is actually not that hard. First, you need to do the standard things for making a new DLL: DLLMain, DLLGetClassObject, implement IClassFactory, et cetera. Then, you need to implement IFunctionDiscoveryProvider. That is it. Well, to get it to work, you will also have to make the proper registrations (hint: use the in box providers registry keys as an example). The second problem will be that your provider will have to implement all of the PnPX stuff to be loaded out of proc because FD will only load known providers inproc by default. If you want to ship a provider, pnpx should be your end game especially if you are doing network type providers. While you are learning the basics, there is a trick to get your FD client to load your non pnpx provider inproc. Note, this is done on the client or provider consumer side. This is how you would do that. I will try to provide a complete sample later if time permits.

hr = pPnpQuery->AddQueryConstraint(
FD_QUERYCONSTRAINT_COMCLSCONTEXT,
FD_CONSTRAINTVALUE_COMCLSCONTEXT_INPROC_SERVER
);

Unit Testing

Since we are between product cycles, they want us to be writing tests and not making changes on the product code until initial planning is done. Testing is like hygiene, not specifically fun, but necessary. Writing unit tests is not my favorite thing to do, but it is need if you want to have quality in your code. So, I went about figuring out how to setup good unit tests in our build environment and how to automate them. I won't go in to those details because they are unique to our environment, but I will provide some slides for a presentation that I gave to my team.

Unit Testing
What Is a Unit Test?
• Unit: smallest testable part of a program: functions, classes, methods
• Validates correct behavior of the unit
• Ideally independent of the other unit tests
• They should cover most code paths
• Generally a white box testing method that is close to the code implementation
• First line of testing
• Should be written in conjunction with the unit of code

What Isn’t Unit Testing?
• A catchall for every bug
• Replacement for other testing
• Functional testing: validates code functions to spec (higher level and more black box than unit tests)
• Integration testing: tests how the units are put together

Why Write Unit Tests?
• Finds bugs early in the development cycle
• Gives confidence that the units you are writing are behaving correctly
• Provides quick feedback if functionality has inadvertently been regressed
• Simplifies refactoring
• Gives confidence when you make late milestone changes
• Documents and defines correct unit behavior

Dev IC Workflow
• Spec & design
• Product code & unit tests
• Check in
• Automation

Unit Test Writing Work Flow
Test Driven Development
TDD Cycle
• Write a test
• Run all unit tests – the new test should fail
• Write some code – write enough code to pass the test
• Run all unit tests – the new test should now pass
• Refactor – clean up the code and tests as needed
• Repeat steps 1-5 until all code units are complete

TDD Benefits
• Promotes better design since you must think about using the API before writing them
• Heavy debugging is rarely needed
• Many of the bugs are found even before it is checked in
• Promotes good unit test coverage
• Discourages code creep
• Unit tests are easier to write before than after

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

Wednesday, August 12, 2009

Creating an IStream or ISequentialStream From a String for XmlLite

XmlLite needs an IStream or an ISequentialStream to parse from. You can get one by opening a file like I showed in the previous post, but in my real code I didn’t have a file, I had a string. No biggie, you can always implement your own if there isn’t one already. This CStringStream class implements ISequentialStream using a string as an input. The class factory method takes in a string, creates a buffer, and gives back an ISequentialStream. Awesome, just what you need if you want to use XmlLite on XML in a string. Here is the class implementation:

#pragma once

//
// this class creates an ISequentialStream from a string
//
class CStringStream : public ISequentialStream
{
public:
// factory method
__checkReturn static HRESULT Create(
__in LPWSTR psBuffer,
__deref_out ISequentialStream **ppStream)
{
HRESULT hr = S_OK;
void *pNewBuff = NULL;
size_t buffSize = 0;

if (!psBuffer)
{
return E_INVALIDARG;
}

*ppStream = NULL;

buffSize = (wcslen(psBuffer)+1) * sizeof(wchar_t);
pNewBuff = malloc(buffSize);

if (!pNewBuff)
{
return E_OUTOFMEMORY;
}

hr = StringCbCopy((LPWSTR)pNewBuff, buffSize, psBuffer);

if (S_OK == hr)
{
*ppStream = new CStringStream(
buffSize,
pNewBuff);
}

if (!*ppStream)
{
hr = E_FAIL;
}

return hr;
};

// ISequentialStream
__checkReturn HRESULT STDMETHODCALLTYPE Read(
__out_bcount_part(cb, *pcbRead) void *pv,
/* [in] */ ULONG cb,
__out_opt ULONG *pcbRead)
{
HRESULT hr = S_OK;

for (*pcbRead = 0; *pcbRead < cb; ++*pcbRead, ++m_buffSeekIndex)
{
// we are seeking past the end of the buffer
if (m_buffSeekIndex == m_buffSize)
{
hr = S_FALSE;
break;
}

((BYTE*)pv)[*pcbRead] = ((BYTE*)m_pBuffer)[m_buffSeekIndex];
}

return hr;
};

HRESULT STDMETHODCALLTYPE Write(
__in_bcount(cb) const void *pv,
/* [in] */ ULONG cb,
__out_opt ULONG *pcbWritten)
{
return E_NOTIMPL;
};

// IUnknown
STDMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&m_cRef);
};

STDMETHODIMP_(ULONG) Release()
{
LONG cRef = InterlockedDecrement(&m_cRef);

if (0 == cRef)
{
delete this;
}

return cRef;
};

STDMETHODIMP 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) || (riid == __uuidof(ISequentialStream)))
{
AddRef();
*ppv = (ISequentialStream*)this;
}
else
{
hr = E_NOINTERFACE;
}
}

return hr;
};

protected:
LONG m_cRef;
void *m_pBuffer;
size_t m_buffSize;
size_t m_buffSeekIndex;

// constructor/deconstructor
CStringStream(
__in size_t buffSize,
__in void *pBuff)
:
m_cRef(1),
m_pBuffer(pBuff),
m_buffSize(buffSize),
m_buffSeekIndex(0)
{
};

~CStringStream()
{
free(m_pBuffer);
};
};

Howto Use XmlLite

I was recently breaking off high-level heavy-weight dependencies on a code I was cleaning up, and I ran into the 500 lb. gorilla that is MSXML6. I found some code that was using it to parse some basic XML strings. MSXML is a full featured XML parser that can do fancy things like schema validation, but it is kind of heavy weight and has high-level dependencies. The downsides of MSXML might be a necessary evil if you need its fancy features, but in many cases we don’t. In my code, I definitely did not. I wanted to gut XML out altogether, but was vetoed. My thoughts turned to MSXML’s handsome and more athletic cousin, XmlLite. XmlLite has very few dependencies and is self-contained in its own library files. Although XmlLite is COM like, it doesn’t even actually have a dependency on COM, so I am liking this guy already. It does need an IStream, or an ISequentialStream, so you will have to create one from some file, or implement the interface yourself. I can provide a sample implementation of that later.


To the code…


Here is a simple quick and dirty code I wrote mainly following the code samples on MSDN. This program takes a filename as a parameter, opens it, and parses the XML printing out the elements. The code I actually wrote looks cleaner, but this will get you going.


MSDN Refrences


MSXML6


XmlLite



/*
* xml_lite.cpp
*
* Description : simple code to show using XML Lite
*/

#include <objbase.h>
#include <XmlLite.h>
#include <shlwapi.h>
#include <stdio.h>

HRESULT WriteAttributes(IXmlReader* pReader)
{
const WCHAR* pwszPrefix;
const WCHAR* pwszLocalName;
const WCHAR* pwszValue;
HRESULT hr = pReader->MoveToFirstAttribute();

if (S_FALSE == hr)
return hr;
if (S_OK != hr)
{
wprintf(L"Error moving to first attribute, error is %08.8lx", hr);
return -1;
}
else
{
while (TRUE)
{
if (!pReader->IsDefault())
{
UINT cwchPrefix;
if (FAILED(hr = pReader->GetPrefix(&pwszPrefix, &cwchPrefix)))
{
wprintf(L"Error getting prefix, error is %08.8lx", hr);
return -1;
}
if (FAILED(hr = pReader->GetLocalName(&pwszLocalName, NULL)))
{
wprintf(L"Error getting local name, error is %08.8lx", hr);
return -1;
}
if (FAILED(hr = pReader->GetValue(&pwszValue, NULL)))
{
wprintf(L"Error getting value, error is %08.8lx", hr);
return -1;
}
if (cwchPrefix > 0)
wprintf(L"Attr: %s:%s=\"%s\" \n", pwszPrefix, pwszLocalName, pwszValue);
else
wprintf(L"Attr: %s=\"%s\" \n", pwszLocalName, pwszValue);
}

if (S_OK != pReader->MoveToNextAttribute())
break;
}
}
return hr;
}

int __cdecl wmain(
__in int argc,
__in_ecount(argc) LPCTSTR argv[])
{
HRESULT hr = S_OK;
IStream *pStream = NULL;
IXmlReader *pReader = NULL;
UINT cAttribute = 0;

if (FAILED(hr = SHCreateStreamOnFile(argv[1], STGM_READ, &pStream)))
{
wprintf(L"Error creating file reader, error is %08.8lx", hr);
return hr;
}

if (FAILED(hr = CreateXmlReader(__uuidof(IXmlReader), (void**) &pReader, NULL)))
{
wprintf(L"error creating xml reader, error is %08.8lx", hr);
return hr;
}

if (FAILED(hr = pReader->SetProperty(XmlReaderProperty_DtdProcessing, DtdProcessing_Prohibit)))
{
wprintf(L"Error setting XmlReaderProperty_DtdProcessing, error is %08.8lx", hr);
return -1;
}

if (FAILED(hr = pReader->SetInput(pStream)))
{
wprintf(L"Error setting input for reader, error is %08.8lx", hr);
return -1;
}

XmlNodeType nodeType;

while (S_OK == (hr = pReader->Read(&nodeType)))
{
LPCWSTR pwszPrefix = NULL;
UINT cwchPrefix = 0;
LPCWSTR pwszLocalName = NULL;
LPCWSTR pwszValue = NULL;

switch (nodeType)
{
case XmlNodeType_XmlDeclaration:
wprintf(L"XmlDeclaration\n");
if (FAILED(hr = WriteAttributes(pReader)))
{
wprintf(L"Error writing attributes, error is %08.8lx", hr);
return -1;
}
break;
case XmlNodeType_Element:
if (FAILED(hr = pReader->GetPrefix(&pwszPrefix, &cwchPrefix)))
{
wprintf(L"Error getting prefix, error is %08.8lx", hr);
return -1;
}
if (FAILED(hr = pReader->GetLocalName(&pwszLocalName, NULL)))
{
wprintf(L"Error getting local name, error is %08.8lx", hr);
return -1;
}
if (cwchPrefix > 0)
wprintf(L"Element: %s:%s\n", pwszPrefix, pwszLocalName);
else
wprintf(L"Element: %s\n", pwszLocalName);

if (FAILED(hr = WriteAttributes(pReader)))
{
wprintf(L"Error writing attributes, error is %08.8lx", hr);
return -1;
}

if (pReader->IsEmptyElement() )
wprintf(L" (empty)");
break;
case XmlNodeType_EndElement:
if (FAILED(hr = pReader->GetPrefix(&pwszPrefix, &cwchPrefix)))
{
wprintf(L"Error getting prefix, error is %08.8lx", hr);
return -1;
}
if (FAILED(hr = pReader->GetLocalName(&pwszLocalName, NULL)))
{
wprintf(L"Error getting local name, error is %08.8lx", hr);
return -1;
}
if (cwchPrefix > 0)
wprintf(L"End Element: %s:%s\n", pwszPrefix, pwszLocalName);
else
wprintf(L"End Element: %s\n", pwszLocalName);
break;
/*
case XmlNodeType_Text:
case XmlNodeType_Whitespace:
if (FAILED(hr = pReader->GetValue(&pwszValue, NULL)))
{
wprintf(L"Error getting value, error is %08.8lx", hr);
return -1;
}
wprintf(L"Text: >%s<\n", pwszValue);
break;
*/
case XmlNodeType_CDATA:
if (FAILED(hr = pReader->GetValue(&pwszValue, NULL)))
{
wprintf(L"Error getting value, error is %08.8lx", hr);
return -1;
}
wprintf(L"CDATA: %s\n", pwszValue);
break;
case XmlNodeType_ProcessingInstruction:
if (FAILED(hr = pReader->GetLocalName(&pwszLocalName, NULL)))
{
wprintf(L"Error getting name, error is %08.8lx", hr);
return -1;
}
if (FAILED(hr = pReader->GetValue(&pwszValue, NULL)))
{
wprintf(L"Error getting value, error is %08.8lx", hr);
return -1;
}
wprintf(L"Processing Instruction name:%S value:%S\n", pwszLocalName, pwszValue);
break;
case XmlNodeType_Comment:
if (FAILED(hr = pReader->GetValue(&pwszValue, NULL)))
{
wprintf(L"Error getting value, error is %08.8lx", hr);
return -1;
}
wprintf(L"Comment: %s\n", pwszValue);
break;
case XmlNodeType_DocumentType:
wprintf(L"DOCTYPE is not printed\n");
break;
}

/*
hr = pReader->GetAttributeCount(&cAttribute);

if (S_OK == hr)
{
wprintf(L"num attrubutes %i\n", cAttribute);
}
*/
}

return hr;
}

Monday, August 10, 2009

SetupDi: How To Enumerate Devices Using SetupDiGetClassDevs

I am back to work from my month off for paternity leave with a fresh new post. This time I going to write a about a topic that is directly related to my job, devices. In particular, we will look at how to use SetupDi to enumerate present devices and print out a few properties. There are a lot of other APIs available in Windows to do device enumeration. Perhaps I will cover them in later posts. As you will see in this post, SetupDi’s interfaces aren’t the most conducive to sexy code, but for better or worse, SetupAPI is the main way to work with devices in Windows. If you have any opinion on what a good device API should look like in Windows, please leave a comment and let me know.


Windows Vista on there is another API that is maybe easier to use for this kind of task, Function Discovery. If you are interested, check it out at:

Function Discovery PnP Enumeration Example

To the code…


This code is pretty basic. We create an HDEVINFO set of all present dev nodes, and step through each dev node printing out a few properties. I haven’t really looked at this sample code recently, so let me know if you see any problems.


#include <windows.h>
#include <setupapi.h>
#include <stdio.h>

void print_property
(
__in HDEVINFO hDevInfo,
__in SP_DEVINFO_DATA DeviceInfoData,
__in PCWSTR Label,
__in DWORD Property
)
{
DWORD DataT;
LPTSTR buffer = NULL;
DWORD buffersize = 0;

//
// Call function with null to begin with,
// then use the returned buffer size (doubled)
// to Alloc the buffer. Keep calling until
// success or an unknown failure.
//
// Double the returned buffersize to correct
// for underlying legacy CM functions that
// return an incorrect buffersize value on
// DBCS/MBCS systems.
//
while (!SetupDiGetDeviceRegistryProperty(
hDevInfo,
&DeviceInfoData,
Property,
&DataT,
(PBYTE)buffer,
buffersize,
&buffersize))
{
if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
{
// Change the buffer size.
if (buffer)
{
LocalFree(buffer);
}
// Double the size to avoid problems on
// W2k MBCS systems per KB 888609.
buffer = (LPTSTR)LocalAlloc(LPTR, buffersize * 2);
}
else
{
break;
}
}

wprintf(L"%s %s\n",Label, buffer);

if (buffer)
{
LocalFree(buffer);
}
}

//int main(int argc, char *argv[], char *envp[])
int setupdi_version()
{
HDEVINFO hDevInfo;
SP_DEVINFO_DATA DeviceInfoData;
DWORD i;

// Create a HDEVINFO with all present devices.
hDevInfo = SetupDiGetClassDevs(
NULL,
0, // Enumerator
0,
DIGCF_PRESENT | DIGCF_ALLCLASSES);

if (INVALID_HANDLE_VALUE == hDevInfo)
{
// Insert error handling here.
return 1;
}

// Enumerate through all devices in Set.

DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData); i++)
{
LPTSTR buffer = NULL;
DWORD buffersize = 0;

print_property(hDevInfo, DeviceInfoData, L"Friendly name :", SPDRP_FRIENDLYNAME);

while (!SetupDiGetDeviceInstanceId(
hDevInfo,
&DeviceInfoData,
buffer,
buffersize,
&buffersize))
{
if (buffer)
{
LocalFree(buffer);
}

if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
{
// Change the buffer size.
// Double the size to avoid problems on
// W2k MBCS systems per KB 888609.
buffer = (LPTSTR)LocalAlloc(LPTR, buffersize * 2);
}
else
{
wprintf(L"error: could not get device instance id (0x%x)\n", GetLastError());
break;
}
}

if (buffer)
{
wprintf(L"\tDeviceInstanceId : %s\n", buffer);
}

print_property(hDevInfo, DeviceInfoData, L"\tClass :", SPDRP_CLASS);
print_property(hDevInfo, DeviceInfoData, L"\tClass GUID :", SPDRP_CLASSGUID);
}


if (NO_ERROR != GetLastError() && ERROR_NO_MORE_ITEMS != GetLastError())
{
// Insert error handling here.
return 1;
}

// Cleanup
SetupDiDestroyDeviceInfoList(hDevInfo);

return 0;
}