Tutorial - Adding WMI Query support to your engine (C++)

Post tutorials on how to do certain tasks within game or engine code here.
Post Reply
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Tutorial - Adding WMI Query support to your engine (C++)

Post by mh »

Here we're going to add support for WMI queries to our engine. Advance warning - it's C++ (so you'll need to write a wrapper in C) and it's Windows only, but at the same time, if you're happy to live with that, you can do a lot of good useful stuff with WMI, such as getting the amount of video RAM you have (useful if you want to scale down those huge textures to something more manageable at load time!), finding your CPU speed, physical memory, disk space, basically just about any hardware or software related parameter you can think of.

This code is based off a DirectX SDK sample (getting video RAM), but was rewritten by myself rather than just being a copy and paste job. While doing that, I reworked it to be a little bit more generally useful.

It's not an all-purpose WMI query engine, but is eminently suitable for simple queries that return single values.

So let's dive in. :D

First thing I did was declare these useful macros in quakedef.h, as a lot of WMI storage info is returned in bytes:

Code: Select all

#define BYTES_TO_MEGS(b) ((b) / 1048576)
#define K_TO_MEGS(b) ((b) / 1024)
#define BYTES_TO_K(b) ((b) / 1024)
Next thing, still in quakedef.h, is to add a #include for sys_wmi.h - just plonk it below the windows.h #include. We're going to get this file next, which is our class definition:

Code: Select all

#include <wbemidl.h>

class WMIQuery
{
public:
	WMIQuery (void);
	VARIANT ExecQuery (const BSTR WMIClassName, const BSTR WMIPropName);
	~WMIQuery (void);

private:
	IWbemLocator *pIWbemLocator;
	IWbemServices *pIWbemServices;
	HRESULT hrCoInitialize;
	BSTR pNameSpace;
};
Now we're going to get the sys_wmi.cpp file; I've sprinkled commen ts through this fairly liberally, so I'm not going to say anything on what it does or how it does it here:

Code: Select all

#include "quakedef.h"
#pragma comment (lib, "wbemuuid.lib")

typedef BOOL (WINAPI *PfnCoSetProxyBlanket)
(
	IUnknown *pProxy,
	DWORD dwAuthnSvc,
	DWORD dwAuthzSvc,
	OLECHAR *pServerPrincName,
	DWORD dwAuthnLevel,
	DWORD dwImpLevel,
	RPC_AUTH_IDENTITY_HANDLE pAuthInfo,
	DWORD dwCapabilities
);


WMIQuery::WMIQuery (void)
{
	// safe defaults
	this->pIWbemLocator = NULL;
	this->pIWbemServices = NULL;

	// result handle for setup
	HRESULT hr;

	// attempt to initialize COM - if this fails, we assume that COM has already been initialized
	// elsewhere rather than that anything dramatic or bad happened - it's 2008 and we don't
	// really expect something as basic as COM initialization to fail any more...
	this->hrCoInitialize = CoInitialize (NULL);

	// connect to default namespace on local computer
	this->pNameSpace = SysAllocString (L"\\\\.\\root\\cimv2");

	// create a locator instance
	hr = CoCreateInstance (CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &this->pIWbemLocator);

	// WMI not working or something else bad happened
	if (FAILED (hr) || !this->pIWbemLocator) return;

	// connect to the COM server
	hr = pIWbemLocator->ConnectServer (this->pNameSpace, NULL, NULL, 0L, 0L, NULL, NULL, &this->pIWbemServices);

	// WMI not working or something else bad happened
	if (FAILED (hr) || !this->pIWbemServices) return;

	// load ole32.dll - calling CoSetProxyBlanket is broken in the API, as is creating an IID_IClientSecurity thingie, so instead
	// we load the dll, GetProcAddress on it, and do it this way - ugleeeeeee!
	HINSTANCE hInstOle32 = LoadLibrary ("ole32.dll");

	if (!hInstOle32) return;

	// set up the client security proxy now
	PfnCoSetProxyBlanket pfnCoSetProxyBlanket = (PfnCoSetProxyBlanket) GetProcAddress (hInstOle32, "CoSetProxyBlanket");

	if (pfnCoSetProxyBlanket)
	{
		pfnCoSetProxyBlanket
		(
			this->pIWbemServices,
			RPC_C_AUTHN_WINNT,
			RPC_C_AUTHZ_NONE,
			NULL,
			RPC_C_AUTHN_LEVEL_CALL,
			RPC_C_IMP_LEVEL_IMPERSONATE,
			NULL,
			0
		);
	}

	FreeLibrary (hInstOle32);
}


VARIANT WMIQuery::ExecQuery (const BSTR WMIClassName, const BSTR WMIPropName)
{
	VARIANT var;

	var.intVal = -1;

	// something went wrong...
	if (!this->pNameSpace) return var;
	if (!this->pIWbemLocator) return var;
	if (!this->pIWbemServices) return var;

	// initial setup
	IEnumWbemClassObject *pIEnumWbemClassObject = NULL;

	// create an enumerator for this class instance
	HRESULT hr = this->pIWbemServices->CreateInstanceEnum (WMIClassName, 0, NULL, &pIEnumWbemClassObject);

	// make sure it worked
	if (SUCCEEDED (hr) && pIEnumWbemClassObject)
	{
		// alloc space for 10 object pointers
		IWbemClassObject *pIWbemClassObject[10] = {NULL};
		DWORD uReturned = 0;

		// get the first 10 (or less if there are less) instances of this class object
		pIEnumWbemClassObject->Reset ();
		hr = pIEnumWbemClassObject->Next (5000, 10, pIWbemClassObject, &uReturned);

		// did we get them?
		if (SUCCEEDED (hr))
		{
			// yes
			for (UINT uClassObj = 0; uClassObj < uReturned; uClassObj++)
			{
				// clear down the variant
				VariantClear (&var);

				// get the memory and release the object
				hr = pIWbemClassObject[uClassObj]->Get (WMIPropName, 0L, &var, NULL, NULL);
				pIWbemClassObject[uClassObj]->Release ();
			}
		}

		// clean up
		pIEnumWbemClassObject->Release ();
	}

	return var;
}


WMIQuery::~WMIQuery (void)
{
	// release COM objects
	if (this->pIWbemServices) this->pIWbemServices->Release ();
	if (this->pIWbemLocator) this->pIWbemLocator->Release ();

	// release the namespace string
	if (this->pNameSpace) SysFreeString (pNameSpace);

	// if COM initialized in here we must uninitialize it to maintain integrity
	if (SUCCEEDED (this->hrCoInitialize)) CoUninitialize ();
}
I've written this so that you only need to create a single instance of WMIQuery, then you can run as many queries as you like. Here's an example:

Code: Select all

void GetAdapterRAM (void)
{
	WMIQuery *TheWMI = new WMIQuery ();

	int NumCPUs = TheWMI->ExecQuery (L"Win32_ComputerSystem", L"NumberOfProcessors").intVal;
	int AdapterRAM = BYTES_TO_MEGS ((TheWMI->ExecQuery (L"Win32_VideoController", L"AdapterRAM")).intVal);
	int CPUSpeed = (TheWMI->ExecQuery (L"Win32_Processor", L"MaxClockSpeed")).intVal;

	delete TheWMI;
}
Have fun!
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Tutorial - Adding WMI Query support to your engine (C++)

Post by Baker »

Woohoo, he's back :D
Post Reply