f
frendguo.

Windows 中遍历进程

这篇文章介绍了在Windows中遍历进程的四种方法: 1. EnumProcesses:常用且简单,但只返回进程ID,需获取进程句柄以获得更多信息。 2. CreateToolhelp32Snapshot:提供详细信息(ID、线程等),但内存占用大且速度最慢。 3. NtQuerySystemInformation:速度快且详细,但需大缓冲区,内存占用高。 4. NtGetNextProcess:

目录
背景效果方法一:`EnumProcesses`示例注意方法二:`CreateToolhelp32Snapshot`示例注意方法三:`NtQuerySystemInformation`示例注意方法四:`NtGetNextProcess`示例注意总结

背景#

Windows 中经常遇到需要遍历所有进程的诉求,Windows 中有多种方式可以遍历进程。

下面将对常规和不常规的方式做一个汇总。

效果#

先看对比效果:

如下图是四个方式遍历一次当前所有进程所花费的时长。可以看到 native 的速度遥遥领先。

插图

那接下来就看看,这几种方式是怎么做的。

方法一:EnumProcesses#

EnumProcesses 是最常用到的,也是最直接的方式了。

示例#

text
DWORD aProcesses[1024] = { 0 }, cbNeeded, cProcesses;
EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded);

注意#

  • EnumProcesses 使用中,无法提前预测当前系统中存在多少个进程,所以通常采取一个大数组的方式来获取。
  • 如果想获取总共多少个进程,可以通过 cbNeeded/sizeof(DWORD) 来获取。
  • 如果 cbNeeded/sizeof(DWORD) 与数组大小一致,可能是因为缓冲区不够,建议将缓冲区增大后再次枚举
  • 只包含进程ID,如果要获取其他信息,需要先通过 OpenProcess 获取进程句柄来获取
  • 枚举速度中偏上

方法二:CreateToolhelp32Snapshot#

还有一种常见的方式,就是通过 CreateToolhelp32Snapshot 拍快照,再通过 Process32Next 来枚举进程的方式。

示例#

text
void EnumProcessBySnap(std::vector<DWORD>& pids) {
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE) {
        std::cerr << "CreateToolhelp32Snapshot (of processes) failed: " << GetLastError() << std::endl;
        return;
    }

    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);

    // Retrieve information about the first process
    if (!Process32First(hProcessSnap, &pe32)) {
        std::cerr << "Process32First failed: " << GetLastError() << std::endl; // show cause of failure
        CloseHandle(hProcessSnap);          // clean the snapshot object
        return;
    }

    do
    {
        pids.push_back(pe32.th32ProcessID);
    } while (Process32Next(hProcessSnap, &pe32));
}

注意#

  • 快照中除了包含进程ID,还包含线程、父进程和优先级信息,所以占用的内存会相对比较大。需要结合自己的诉求
  • 枚举速度最慢
  • 占用的内存最大

方法三:NtQuerySystemInformation#

通过 NtQuerySystemInformation + **SystemProcessInformation** 可以获取到包含 **SYSTEM_PROCESS_INFORMATION** 结构的数组。

示例#

text
void EnumProcessByQueryInformation(std::vector<DWORD>& pids) {
    DWORD   dwSize = 0xFFFFF;
    LPTSTR pBuf = new TCHAR[dwSize];
    PSYSTEM_PROCESS_INFORMATION pInfo = { 0 };

    pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuf;
    auto status = NtQuerySystemInformation(SystemProcessInformation, pInfo, dwSize, &dwSize);
    if (!NT_SUCCESS(status)) {
        std::cerr << "NtQuerySystemInformation failed: " << status << std::endl;
        return;
    }

    while (pInfo->NextEntryOffset)
    {
        pids.push_back(HandleToULong(pInfo->UniqueProcessId));

        pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryOffset);
    }
}

注意#

  • EnumProcesses 一样,缓冲区需要采用大数组来存储,缓冲区不够的时候,会报错
  • 包含的信息较多,具体看 SYSTEM_PROCESS_INFORMATION 结构
  • 由于其调用链较短,在获取足够数据的情况下,速度较快
  • 占用内存较多

方法四:NtGetNextProcess#

此方式是一个导出但未文档化的接口,可以从 或者 phnt 中找到对应的接口。

示例#

text
void EnumProcess(std::vector<DWORD>& pids) {
    HANDLE hProcess = nullptr;
    HANDLE nextProcess = NULL;
    while (NT_SUCCESS(NtGetNextProcess(hProcess, PROCESS_QUERY_INFORMATION, 0, 0, &nextProcess))) {
        auto pid = GetProcessId(nextProcess);
        pids.push_back(pid);
        hProcess = nextProcess;
    }
}

注意#

  • 使用此方式,请确保与运行的系统是兼容的。可以看对应版本的 ntoskrnl.exe 有无导出此接口
  • 其返回的进程句柄,可以用来获取你想要的信息
  • 此方法直接读取内核对象的链表,所以跟用户态拿到的进程列表存在出入。0(System Idle Process)、4(System)、332(Secure system)、374(Registry)等进程无法获取,还有部分进程内核进程对象存在,但用户态进程不一定存在,需要额外进行处理。请谨慎使用。
  • 获取速度最快(比其他方式快了近10倍)

总结#

不同的方式可以适应不同的诉求,各有利弊,需要了解详细后再选择使用。

── END ──
↑ Back to topSubscribe
← NEWER
Windows 任务管理器是怎么启用/禁用应用自启动的
OLDER →
Windows 应用性能关键字段解析

还没有评论。下面留下第一条吧。

+ 留个评论
提交后由管理员审核 · 通常 24h 内显示
Jul 25Windows 权限相关概念解析4m →Mar 25Windows是如何区分互联网下载文件和本地文件的6m →Feb 25如何低成本的获取到应用卡顿情况3m →