Win32API中WMI IWbemServices::ExecMethod调用过慢问题咨询
问题
我需要获取Windows系统中所有运行进程的信息,目前通过WMI的IWbemServices::ExecMethod调用Win32_Process类的GetOwner方法来获取进程所有者的用户名和域名,代码实现如下:
HRESULT hres; BSTR MethodName = SysAllocString(L"GetOwner"); BSTR ClassName = SysAllocString(L"Win32_Process"); // get the object containing our desired method IWbemClassObject* pClass = NULL; hres = m_pSvc->GetObject(ClassName, 0, NULL, &pClass, NULL); if (FAILED(hres)) { printf("GetObject hres = %08x\n", hres); } // get the desired method // in our case, we only need pmethodGetOwner since GetOwner really only has output IWbemClassObject* pMethod_GetOwner = NULL; hres = pClass->GetMethod(MethodName, 0, NULL, &pMethod_GetOwner); if (FAILED(hres)) { printf("GetMethod hres = %08x\n", hres); } // spawn the instance of the method IWbemClassObject* pInInst = NULL; hres = pMethod_GetOwner->SpawnInstance(0, &pInInst); if (FAILED(hres)) { printf("SpawnInstance hres = %08x\n", hres); } IEnumWbemClassObject* pEnumerator = NULL; BSTR wql = SysAllocString(L"WQL"); BSTR query = SysAllocString(L"SELECT * FROM Win32_Process"); hres = m_pSvc->ExecQuery( wql, query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); SysReleaseString(wql); SysReleaseString(query); if (FAILED(hres)) { qDebug() << "ExecQuery failed" << " Error code = 0x" << std::hex << hres << '\n'; qDebug()<< _com_error(hres).ErrorMessage() << '\n'; return std::vector<DCProcessInfo>(); // Program has failed. } // Get the data from the WQL sentence IWbemClassObject *pclsObj = NULL; ULONG uReturn = 0; std::vector<DCProcessInfo> process_list{}; double seconds{}; while (pEnumerator) { DCProcessInfo proc_info; HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); if(0 == uReturn || FAILED(hr)) break; VARIANT vtProp; hr = pclsObj->Get(L"Name",0,&vtProp,NULL,NULL); if(SUCCEEDED(hr)) proc_info.name = QString::fromWCharArray(vtProp.bstrVal); VariantClear(&vtProp); hr = pclsObj->Get(L"ProcessId",0,&vtProp,NULL,NULL); if(SUCCEEDED(hr)) proc_info.pid = vtProp.uintVal; VariantClear(&vtProp); hr = pclsObj->Get(L"CreationDate",0,&vtProp,NULL,NULL); if(SUCCEEDED(hr)) proc_info.creation_date = tools::fromBSTRToDateTime(vtProp.bstrVal); VariantClear(&vtProp); hr = pclsObj->Get(L"ExecutablePath",0,&vtProp,NULL,NULL); if(SUCCEEDED(hr)) proc_info.file_path = QString::fromWCharArray(vtProp.bstrVal); VariantClear(&vtProp); hr = pclsObj->Get(L"CommandLine",0,&vtProp,NULL,NULL); if(SUCCEEDED(hr)) proc_info.command_line = QString::fromWCharArray(vtProp.bstrVal); VariantClear(&vtProp); // std::pair<QString,QString> user_domain = get_process_user_domain(pclsObj); // proc_info.owner_user = user_domain.first; // proc_info.owner_domain = user_domain.second; VARIANT vtVal; std::pair<QString,QString> user_dom{}; hr =pclsObj->Get(L"__PATH",0,&vtVal,0,0); if (FAILED(hr)) { qDebug()<<"get_process_user_domain() failed Get(__PATH)"; pClass->Release(); pMethod_GetOwner->Release(); pInInst->Release(); //return user_dom; } std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now(); hr = m_pSvc->ExecMethod(vtVal.bstrVal, MethodName, 0, NULL, NULL, &pMethod_GetOwner, NULL); if (FAILED(hr)) { qDebug()<<"get_process_user_domain() failed ExecMethod against" << QString::fromWCharArray(V_BSTR(&vtVal)); pclsObj->Release(); continue; } VariantClear(&vtVal); auto end = std::chrono::steady_clock::now(); std::chrono::duration<double> elapsed_seconds = end-start; qDebug()<<elapsed_seconds.count(); hr = pMethod_GetOwner->Get(L"User", 0, &vtVal, NULL, 0); if (FAILED(hr)) { qDebug()<<"get_process_user_domain() failed Get(User)"; pClass->Release(); pMethod_GetOwner->Release(); pInInst->Release(); //return user_dom; } else proc_info.owner_user = QString::fromWCharArray(vtProp.bstrVal); VariantClear(&vtVal); hr = pMethod_GetOwner->Get(L"Domain", 0, &vtVal, NULL, 0); if (FAILED(hr)) { qDebug()<<"get_process_user_domain() failed Get(Domain)"; //return user_dom; } else proc_info.owner_domain = QString::fromWCharArray(vtProp.bstrVal); VariantClear(&vtVal); process_list.push_back(proc_info); pclsObj->Release(); pclsObj=NULL; } qDebug()<<"time passed:"<<seconds; pClass->Release(); pMethod_GetOwner->Release(); pInInst->Release(); return process_list;
但发现每次ExecMethod调用耗时约0.2秒,当调用200次时整体效率极低。想咨询是否我的使用方式存在问题,或是该方法本身就如此缓慢?若本身性能如此,是否有其他替代方案?我曾尝试用CreateToolhelp32Snapshot枚举进程,但它无法提供足够的信息,在调用GetOwner方法前WMI的表现是高效的。
分析与解决方案
1. WMI调用慢的原因
你当前的用法没有问题,Win32_Process::GetOwner通过ExecMethod调用本身就存在性能瓶颈:
- 每次
ExecMethod都是一次独立的WMI请求,涉及进程间通信(WMI服务在单独进程中运行),单请求0.2秒是典型的开销。 - 批量对200个进程逐个调用时,累计开销会被放大到40秒以上,完全无法满足效率要求。
2. 替代方案:使用原生Windows API(高效首选)
直接调用Windows原生API组合获取进程信息及所有者,性能远高于WMI,步骤如下:
- 枚举进程:用
CreateToolhelp32Snapshot或EnumProcesses枚举所有进程。 - 获取进程基础信息:
- 进程名、路径:通过
OpenProcess打开进程后,用QueryFullProcessImageNameW获取路径,再解析进程名。 - 创建时间:用
GetProcessTimes获取进程创建时间戳。 - 命令行:用
NtQueryInformationProcess(需链接ntdll.lib)获取目标进程的命令行信息。
- 进程名、路径:通过
- 获取进程所有者:
- 用
OpenProcess打开目标进程,指定PROCESS_QUERY_LIMITED_INFORMATION权限(Windows Vista及以上可用,权限要求低,不易失败)。 - 用
OpenProcessToken打开进程的访问令牌。 - 用
GetTokenInformation获取令牌的TokenUser信息,得到用户SID。 - 用
LookupAccountSidW将SID转换为用户名和域名。
- 用
示例代码片段(获取进程所有者部分):
#include <windows.h> #include <sddl.h> #include <tlhelp32.h> #include <vector> #include <string> bool GetProcessOwner(DWORD pid, std::wstring& outUser, std::wstring& outDomain) { HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); if (!hProcess) return false; HANDLE hToken = NULL; if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { CloseHandle(hProcess); return false; } DWORD tokenInfoSize = 0; GetTokenInformation(hToken, TokenUser, NULL, 0, &tokenInfoSize); std::vector<BYTE> tokenInfoBuffer(tokenInfoSize); PTOKEN_USER pTokenUser = reinterpret_cast<PTOKEN_USER>(tokenInfoBuffer.data()); if (!GetTokenInformation(hToken, TokenUser, pTokenUser, tokenInfoSize, &tokenInfoSize)) { CloseHandle(hToken); CloseHandle(hProcess); return false; } WCHAR userName[256], domainName[256]; DWORD userNameLen = _countof(userName), domainNameLen = _countof(domainName); SID_NAME_USE sidType; if (LookupAccountSidW(NULL, pTokenUser->User.Sid, userName, &userNameLen, domainName, &domainNameLen, &sidType)) { outUser = userName; outDomain = domainName; } CloseHandle(hToken); CloseHandle(hProcess); return true; }
3. WMI优化方案(仅当必须用WMI时)
如果无法切换到原生API,可尝试批量WMI查询:
使用WQL关联查询,结合Win32_Process和Win32_SessionProcess、Win32_UserAccount,一次性获取所有进程的所有者信息,但实际优化效果有限,仍不如原生API高效。
内容的提问来源于stack exchange,提问作者Nikita Novikov
相关产品推荐
相关产品推荐

