C++使用WMI获取用户名时遭遇COM初始化顺序错误
C++中使用WMI获取用户名时的COM初始化错误问题
我在C++中尝试使用WMI获取计算机用户名时遇到了错误:
Error: Security must be initialized before any interfaces are marshalled or unmarshalled. It cannot be changed once initialized.
当调换CoInitialize()与CoInitializeSecurity()的调用顺序后,又出现如下错误:
Error: CoInitialize has not been called.
相关代码如下:
HRESULT hres; std::string userNametest; // Initialize COM hres = CoInitialize(NULL); if (FAILED(hres)) { _com_error err(hres); LPCTSTR errMsg = err.ErrorMessage(); std::wstring w = errMsg; userNametest = std::string(w.begin(), w.end()); CoUninitialize(); if (jadedevlog.is_open()) { jadedevlog << "Error: " << userNametest << endl; jadedevlog.close(); } return 1; } // Initialize security hres = CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); if (FAILED(hres)) { _com_error err(hres); LPCTSTR errMsg = err.ErrorMessage(); std::wstring w = errMsg; userNametest = std::string(w.begin(), w.end()); if (jadedevlog.is_open()) { jadedevlog << "Error: " << userNametest << endl; jadedevlog.close(); } CoUninitialize(); return 1; } // Obtain the initial locator to WMI IWbemLocator* pLoc = NULL; hres = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc); if (FAILED(hres)) { CoUninitialize(); userNametest = "fail to create WbemLocator"; // Failed to create WbemLocator if (jadedevlog.is_open()) { jadedevlog << "Error: " << userNametest << endl; jadedevlog.close(); } return 1; } // Connect to WMI through the IWbemLocator::ConnectServer method IWbemServices* pSvc = NULL; hres = pLoc->ConnectServer( L"ROOT\CIMv2", // WMI namespace NULL, NULL, 0, NULL, 0, 0, &pSvc); if (FAILED(hres)) { pLoc->Release(); CoUninitialize(); userNametest = "Cant connect to WMI"; // Could not connect to WMI if (jadedevlog.is_open()) { jadedevlog << "Error: " << userNametest << endl; jadedevlog.close(); } return 1; } // Set security levels on the proxy hres = CoSetProxyBlanket( pSvc, // Indicates the proxy to set RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx NULL, // Server principal name RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx NULL, // client identity EOAC_NONE // proxy capabilities ); if (FAILED(hres)) { pSvc->Release(); pLoc->Release(); CoUninitialize(); userNametest = "Could not set proxy blanket"; // Could not set proxy blanket if (jadedevlog.is_open()) { jadedevlog << "Error: " << userNametest << endl; jadedevlog.close(); } return 1; } // Use the IWbemServices pointer to make requests of WMI IEnumWbemClassObject* pEnumerator = NULL; hres = pSvc->ExecQuery( L"WQL", L"SELECT * FROM Win32_ComputerSystem", WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); if (FAILED(hres)) { pSvc->Release(); pLoc->Release(); CoUninitialize(); userNametest = "fail to query for Win32_ComputerSystem"; // Query for Win32_ComputerSystem failed if (jadedevlog.is_open()) { jadedevlog << "Error: " << userNametest << endl; jadedevlog.close(); } return 1; } // Retrieve the data from the query in step 6 IWbemClassObject* pclsObj = NULL; ULONG uReturn = 0; while (pEnumerator) { HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); if (0 == uReturn) { userNametest = "no more time"; break; // No more items } VARIANT vtProp; // Get the value of the "UserName" property hr = pclsObj->Get(L"UserName", 0, &vtProp, 0, 0); int wslen = SysStringLen(vtProp.bstrVal); int len = WideCharToMultiByte(CP_UTF8, 0, vtProp.bstrVal, wslen, nullptr, 0, nullptr, nullptr); if (len <= 0) userNametest = "Convertion fail"; //convert from BSTR to string fails std::string result(len, 0); WideCharToMultiByte(CP_UTF8, 0, vtProp.bstrVal, wslen, &result[0], len, nullptr, nullptr); VariantClear(&vtProp); pclsObj->Release(); userNametest = result; } // Cleanup pSvc->Release(); pLoc->Release(); pEnumerator->Release(); CoUninitialize();
请问这是什么原因?
问题原因及解决办法
这两个错误的核心是COM安全初始化的时机规则:
- 第一个错误:调用
CoInitialize()后,COM会自动为当前线程初始化默认安全设置,之后再手动调用CoInitializeSecurity()就会触发错误——因为COM安全设置一旦初始化(自动或手动)就无法修改。 - 第二个错误:
CoInitializeSecurity()必须在单线程单元(STA)上下文调用,调用前必须先完成线程的COM初始化(CoInitialize()或CoInitializeEx()),直接调用会导致COM上下文未就绪。
正确处理方式
- 严格控制初始化顺序:先调用
CoInitializeEx()(推荐,明确指定STA模式)或CoInitialize(),立即调用CoInitializeSecurity(),中间不能有任何其他COM操作(比如CoCreateInstance()),否则COM会自动初始化安全设置,导致后续手动调用失败。 - 忽略特定错误:
CoInitializeSecurity()返回RPC_E_TOO_LATE时,说明COM已自动初始化安全设置,可忽略该错误继续执行。 - 修正字符串转义:代码中
L"ROOT\CIMv2"的反斜杠需转义为L"ROOT\\CIMv2",否则会被解析为转义字符导致WMI连接失败。
修正后的关键代码片段
// 初始化COM,明确指定STA模式 HRESULT hres = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (FAILED(hres)) { // 错误处理逻辑... return 1; } // 立即初始化安全,忽略RPC_E_TOO_LATE错误 hres = CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); if (FAILED(hres) && hres != RPC_E_TOO_LATE) { // 错误处理逻辑... CoUninitialize(); return 1; } // 后续执行CoCreateInstance等COM操作 IWbemLocator* pLoc = NULL; hres = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc); // ...其余逻辑
内容的提问来源于stack exchange,提问作者Miranda Qi
相关产品推荐
相关产品推荐

