You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)获取目标进程的命令行信息。
  • 获取进程所有者:
    1. 用OpenProcess打开目标进程,指定PROCESS_QUERY_LIMITED_INFORMATION权限(Windows Vista及以上可用,权限要求低,不易失败)。
    2. 用OpenProcessToken打开进程的访问令牌。
    3. 用GetTokenInformation获取令牌的TokenUser信息,得到用户SID。
    4. 用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 13:47:11