I recently had the need to be able to query the WMI via WQL (which are very fascinating and helpful technologies, actually) from C++. WQL essentially provides an SQL interface for querying system properties of a running computer.
The process of running and returning a WQL query from C++ has several steps to it, so I found the official example for how to do this on Microsoft's website.
I will not quote the full original code here, but I will comment on it. It makes a pretty good object lesson in how to not program in C++.
While not a fault of the code sample author it is a no-no and does result in making memory management much harder. Also, it leads right into point #2.
It fails to call
pclsObj->Release()after each inner loop operation and also fails to callpEnumerator->Release()after the query execution is completed. I commented on this on the MSDN page, hopefully the sample will get fixed.I verified these memory leaks by watching memory usage in debug mode of Visual Studio.
if (FAILED(hres)) { cout << "Query for processes failed. " << "Error code = 0x" << hex << hres << endl; pSvc->Release(); pLoc->Release(); CoUninitialize(); return 1; // Program has failed. }The above is repeated in whole or in part five times throughout the example.
Without RAII techniques it is very difficult to guarantee that all resources will be freed and the state of
CoInitialize()will be returned to normal when an error occurs or even when the code exits normally.By putting RAII to use we are able to reduce our error handling code to simple C++ exception throwing and we can let the process of stack unwinding cleanup the state of the system for us.
While not specifically a problem with the sample, if you need to provide the ability to perform WQL queries in crossplatform capable code (obviously either returning nothing or throwing an exception on non-windows platforms) you need to be able to return std::strings instead of BSTR values. I have enhanced the example to show how to convert a VARIANT of any type to a BSTR VARIANT then to convert the resulting BSTR to a standard C++ string.
Our "refined" version follows:
#include <vector> #include <string> #include <sstream> #include <map> #define _WIN32_DCOM #include <iostream> using namespace std; #include <comdef.h> #include <Wbemidl.h> # pragma comment(lib, "wbemuuid.lib") std::wstring str_to_wstr( const std::string& str ) { std::wstring wstr( str.length()+1, 0 ); MultiByteToWideChar( CP_ACP, 0, str.c_str(), str.length(), &wstr[0], str.length() ); wstr.resize(str.length()); return wstr; } std::string wstr_to_str( const std::wstring& wstr ) { size_t size = wstr.length(); std::string str( size + 1, 0 ); WideCharToMultiByte( CP_ACP, 0, wstr.c_str(), size, &str[0], size, NULL, NULL ); str.resize(wstr.length()); return str; } template<typename T> std::string to_string(const T &t) { std::stringstream ss; ss << t; return ss.str(); } struct WMI { typedef std::vector<std::map<std::string, std::string> > Dataset; //Struct to initialize and deinitialize COM state. struct CoInitializeGuard { CoInitializeGuard() { // Initialize COM. HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED); if (FAILED(hres)) { throw std::runtime_error("Failed to initialize COM Library. Error code " + to_string(hres)); } } ~CoInitializeGuard() { CoUninitialize(); } }; //Manages call to CoInitializedSecurity struct CoInitializeSecurityGuard { CoInitializeSecurityGuard() { // Initialize HRESULT hres = CoInitializeSecurity( NULL, -1, // COM negotiates service NULL, // Authentication services NULL, // Reserved RPC_C_AUTHN_LEVEL_DEFAULT, // authentication RPC_C_IMP_LEVEL_IMPERSONATE, // Impersonation NULL, // Authentication info EOAC_NONE, // Additional capabilities NULL // Reserved ); if (FAILED(hres)) { throw std::runtime_error("Failed to initialize security. Error code " + to_string(hres)); } } ~CoInitializeSecurityGuard() { } }; //Manages lifetime of IWbemLocator object struct CoCreateInstanceGuard { // Obtain the initial locator to Windows Management // on a particular host computer. IWbemLocator *pLoc; CoCreateInstanceGuard() : pLoc(0) { HRESULT hres = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLoc); if (FAILED(hres)) { throw std::runtime_error("Failed to create IWbemLocator object. Error code " + to_string(hres)); } } ~CoCreateInstanceGuard() { pLoc->Release(); } }; //Manages lifetime of IWbemServices object struct ConnectServerGuard { IWbemServices *pSvc; ConnectServerGuard(IWbemLocator *pLoc) : pSvc(0) { // Connect to the root\cimv2 namespace with the // current user and obtain pointer pSvc // to make IWbemServices calls. HRESULT hres = pLoc->ConnectServer( _bstr_t(L"ROOT\\CIMV2"), // WMI namespace NULL, // User name NULL, // User password 0, // Locale NULL, // Security flags 0, // Authority 0, // Context object &pSvc // IWbemServices proxy ); if (FAILED(hres)) { throw std::runtime_error("Could not connect to WMI Server. Error code " + to_string(hres)); } } ~ConnectServerGuard() { pSvc->Release(); } }; //Manages call to CoSetProxyBlanket struct CoSetProxyBlanketGuard { CoSetProxyBlanketGuard(IWbemServices *pSvc) { // Set the IWbemServices proxy so that impersonation // of the user (client) occurs. HRESULT hres = CoSetProxyBlanket( pSvc, // the proxy to set RPC_C_AUTHN_WINNT, // authentication service RPC_C_AUTHZ_NONE, // authorization service NULL, // Server principal name RPC_C_AUTHN_LEVEL_CALL, // authentication level RPC_C_IMP_LEVEL_IMPERSONATE, // impersonation level NULL, // client identity EOAC_NONE // proxy capabilities ); if (FAILED(hres)) { throw std::runtime_error("Could not set proxy blanket. Error code " + to_string(hres)); } } ~CoSetProxyBlanketGuard() { } }; CoInitializeGuard cig; CoInitializeSecurityGuard cisg; CoCreateInstanceGuard ccig; ConnectServerGuard csg; CoSetProxyBlanketGuard cspb; //By using RAII WMI is only created if all phases succeed. //if any piece fails then the resources are freed that did succeed //during stack unrolling of the WMI object. // //Also, the destruction of the WMI object under normal circumstances //similarly results in the unrolling of each piece and the resources being //returned. WMI() : csg(ccig.pLoc), cspb(csg.pSvc) { } void runQuery(const std::string &query) { // Use the IWbemServices pointer to make requests of WMI. // Make requests here: // For example, query for all the running processes IEnumWbemClassObject* pEnumerator = NULL; HRESULT hres = csg.pSvc->ExecQuery( bstr_t("WQL"), bstr_t(query.c_str()), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); if (FAILED(hres)) { throw std::runtime_error("WMI query failed. Error code " + to_string(hres)); } else { IWbemClassObject *pclsObj; ULONG uReturn = 0; while (pEnumerator) { hres = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); if(0 == uReturn) { break; } if (pclsObj->BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY) == WBEM_S_NO_ERROR) { BSTR name; VARIANT value; while (pclsObj->Next(0, &name, &value,0,0) == WBEM_S_NO_ERROR) { // Get the value of the Name property VariantChangeType(&value, &value, 0, VT_BSTR); //Note: unicode characters are not transliterated, the are preserved if they // exist and may case display problems. cout << wstr_to_str(name) << ": " << (value.vt==VT_BSTR)?wstr_to_str(value.bstrVal):"" << endl; VariantClear(&value); SysFreeString(name); } pclsObj->Release(); } } pEnumerator->Release(); } } }; int main(int argc, char* argv[]) { WMI wmi; wmi.runQuery("select Dependent from win32_usbcontrollerdevice"); return 0; // Program successfully completed. }
Comments
C2563 error under vc++ 2008 express
I got a C2563 error message compiling under vc++ 2008 express. I had to place parentheses around the whole ternary in the cout statement for it to compile, like so:
Thanks for the code! I started using the microsoft example and changing it to support 2008 express (which doesn't support mfc/atl) using std. I wish I found this yesterday!
Interesting
I actually developed this code under VC++ 2008 Express Edition. I'm curious what compiler option(s) I had that were different. Glad it has proven to be useful to you.