Inside3D!
     

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

 
Post new topic   Reply to topic    Inside3d Forums Forum Index -> Programming Tutorials
View previous topic :: View next topic  
Author Message
mh



Joined: 12 Jan 2008
Posts: 910

PostPosted: Thu Nov 27, 2008 9:31 pm    Post subject: Tutorial - Adding WMI Query support to your engine (C++) Reply with quote

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. Very Happy

First thing I did was declare these useful macros in quakedef.h, as a lot of WMI storage info is returned in bytes:
Code:
#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:
#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:
#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:
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!
_________________
DirectQ Engine - New release 1.8.666a, 9th August 2010
MHQuake Blog (General)
Direct3D 8 Quake Engines
Back to top
View user's profile Send private message Visit poster's website
Baker



Joined: 14 Mar 2006
Posts: 1538

PostPosted: Fri Nov 28, 2008 12:09 am    Post subject: Re: Tutorial - Adding WMI Query support to your engine (C++) Reply with quote

Woohoo, he's back Very Happy
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Inside3d Forums Forum Index -> Programming Tutorials All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2004 phpBB Group