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

Friday, July 10, 2009

Removing ATL from Your Code

Removing ATL from Your Code
COM is all over the place if you are doing win32 programming especially in higher level components like the shell. Should we be using COM as a Windows extension model in general is debatable, but we can leave that discussion for a later time. We want to talk about ATL.

The point of ATL is that it is supposed to help you write COM code more quickly by taking care of the tedious parts of COM programming like implementing IUnknown, managing ref counts, and freeing up memory; however, without going into too much gory detail, using ATL adds many high level dependencies not introduced by simple COM programming. If you are writing a higher level COM object that is already depending on things that use ATL or other high level dependencies, then maybe it doesn’t really matter if you are using ATL. Then it is really just a matter of preference. If you want to limit your high level dependencies and lower the level of you COM object, then you should avoid taking a dependency on ATL. Generally this is what you want to do if you want to write a low level system component that others will depend on and write extensions. I fall into this latter category for a component I want to clean up.

Unfortunately you may have some legacy COM code that took a dependency on ATL. If someone wanted depend on your component for use for something low level, then ATL could be a deal breaker. That is if COM already isn’t, but there are ways to make COM lean and mean without even using OLE. Take UMDF for example. This scenario is precisely the situation I am in. After coming back from vacation, I will be spending the next while removing ATL from some COM components. I will write on the things you need to do as I go through and figure out the processes.

Thursday, July 9, 2009

Supressing PreFAST Warnings

PreFAST is a great static analysis tool that can find lots of bugs for you; however, sometimes it can act like an over protective mother. In my last posts I NULL terminated the strings just to make PreFAST happy, but it felt more like a hack. I don't like leaving hacks in my real code. If there is a PreFAST warning that you feel like is unjustified and you would like you code to PreFAST warning free, you can use a handy #pragma trick to tell PreFAST that you know what you are doing and its okay. Keep in mind you don't want to do this very often, because generally PreFAST warnings should be fixed.

In my case, I was mallocing a buffer for a string that was to be read from the registry. PreFAST was warning me that I should NULL terminate the string. In this case RegEnumValue should do that correctly or give me an error. Since this warning is safe to ignore and I don't want to put in a hack just to get rid of the warning I added this #pragma at the line where the warning was:

#pragma prefast(suppress: 26036, "We expect that RegEnumValue will properly NULL terminate ppszKeyValue.")

Wednesday, July 8, 2009

Reading Registry Values Using RegEnumValue

This time I am pretending that I don’t know or care what the name of the value is. You can do that by using RegEnumValue. I wrote a function with this prototype:


__checkReturn HRESULT ReadKeyValue(
__in LPCWSTR pszSubKey,
__in DWORD dwIndex,
__deref_out LPWSTR *ppszKeyValue);

Basically you can read regkey values by its index. If you wanted to print out all of the values you could do it like this:

DWORD keyIndex = 0;

do
{
hr = HRESULT_FROM_WIN32(ReadKeyValue(psCategory, psSubcategory, keyIndex++, &psKeyValue));
wprintf(L"%s 0x%x\n", psKeyValue, hr);
if (hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) wprintf(L"this means there are no more key values\n");
} while (S_OK == hr);

So here is the complete function. If you knew that you were going to read a lot of values, you might want to open the regkey handle beforehand, do all of your reads, and close it when you are done. Other than that this code should look similar to the previous posting.

__checkReturn HRESULT ReadKeyValue(
__in LPCWSTR pszSubKey,
__in DWORD dwIndex,
__deref_out LPWSTR *ppszKeyValue)
{
HRESULT hr = S_OK;
LPWSTR pszSubKey = NULL;
HKEY hKey = NULL;
LPWSTR pszValueName = NULL;
DWORD lpcchValueName = 2048; // a reasonable initial buffer size
DWORD cbDataSize = 4096;
DWORD type = 0;

hr = HRESULT_FROM_WIN32(
RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
pszSubKey,
0,
KEY_QUERY_VALUE,
&hKey));

if (S_OK == hr)
{
*ppszKeyValue = (LPWSTR)malloc(cbDataSize);
pszValueName = (LPWSTR)malloc(lpcchValueName * sizeof(wchar_t));

if (!*ppszKeyValue || !pszValueName)
{
hr = E_OUTOFMEMORY;
}
else
{
**ppszKeyValue = L'\0';
*pszValueName = L'\0';
}
}

while (S_OK == hr && S_OK != (hr = HRESULT_FROM_WIN32(
RegEnumValue(
hKey,
dwIndex,
pszValueName,
&lpcchValueName ,
NULL,
&type,
(LPBYTE)*ppszKeyValue,
&cbDataSize))))
{
if (*ppszKeyValue)
{
free(*ppszKeyValue);
*ppszKeyValue = NULL;
}

if (pszValueName)
{
free(pszValueName);
pszValueName = NULL;
}

if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) == hr)
{
hr = S_OK;

*ppszKeyValue = (LPWSTR)malloc(cbDataSize);
pszValueName = (LPWSTR)malloc(lpcchValueName * sizeof(wchar_t));

if (!*ppszKeyValue || !pszValueName)
{
hr = E_OUTOFMEMORY;
}
else
{
**ppszKeyValue = L'\0'; // PreFAST is still whining about this
*pszValueName = L'\0';
}
}
else
{
break;
}
}

// cleanup
(void)RegCloseKey(hKey);

if (pszValueName)
{
free(pszValueName);
}

if (S_OK != hr && *ppszKeyValue)
{
free(*ppszKeyValue);
*ppszKeyValue = NULL;
}

return hr;
}

Posting Source Code in Blogger

It looks like blogger does not have a good built-in way to post code which is lame since this blog is supposed to be about code. For my last post I used a <pre> tag so the code didn't get foobared. The <pre> tag tells your browser to use a fixed width font and preserve the spacing. I supposed that is the easiest fix, and I will go with it for now.

Googling I found this (santaxhighlighter) other solution that looks more robust. I will do all of the santax highlighting and what not. It looks like it could be nice, but more overhead and annoyance.

I guess I could also use something like Word 2007 to publish to Blogger along with all of its bloated html.

It would be great if Blogger just provided a code mode.