1

任务是:通过可执行文件名查找进程ID。
调用应用程序是 32 位的,查找进程可以是 32 位或 64 位

解决方案:

#include <string>
#include <iostream>

#include <windows.h>
#include <Tlhelp32.h>
#include <psapi.h>
#pragma comment(lib, "psapi.lib")

size_t r_wcsstr(const wchar_t* str, const wchar_t* search)
{
    for (size_t i = wcslen(str) - wcslen(search); i > 0; --i)
    {
        if (wcsstr(str + i, search) != NULL)
            return i + 1;
    }

    return -1;
}

bool find_process_1(const std::wstring& name, DWORD& pid)
{
    DWORD aProcesses[1024] { 0 };
    DWORD cbNeeded { 0 };
    DWORD cProcesses { 0 };

    unsigned int i;

    if (EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded) == 0)
        return false;

    cProcesses = cbNeeded / sizeof(DWORD);

    for (i = 0; i < cProcesses; i++)
    {
        WCHAR module_name[MAX_PATH] { 0 };
        HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, aProcesses[i]);

        if (process == NULL || 
            GetProcessImageFileNameW(process, module_name, sizeof(module_name) / sizeof(WCHAR)) == 0)
            continue;

        size_t pos = r_wcsstr(module_name, name.c_str());

        if (pos != -1)
        {
            pid = aProcesses[i];
                return true;
        }
    }

    return false;
}

bool find_process_2(const std::wstring& name, DWORD& pid)
{
    HANDLE snapshot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32 process_entry = { 0 };
    process_entry.dwSize = sizeof(process_entry);
    bool found = false;

    snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

    if (snapshot == INVALID_HANDLE_VALUE)
        return false;

    BOOL success = Process32First(snapshot, &process_entry);
    while (success == TRUE)
    {
        if (_wcsicmp(process_entry.szExeFile, name.c_str()) == 0)
        {
            pid = process_entry.th32ProcessID;
            CloseHandle(snapshot);
            return true;
        }

        success = Process32Next(snapshot, &process_entry);
    }

    CloseHandle(snapshot);

    return false;
}

int main(int argc, WCHAR **argv)
{
    unsigned long pid { 0 };

    unsigned long long total { 0 };

    for (int i = 0; i < 1000; ++i)
    {
        unsigned long long start = GetTickCount64();

        find_process_1(L"Calculator.exe", pid);

        total += (GetTickCount64() - start);
    }

    std::wcout << L"Total: " << total << L"\tper call: " << total / 1000. << std::endl;

    total = 0;

    for (int i = 0; i < 1000; ++i)
    {
        unsigned long long start = GetTickCount64();

        find_process_2(L"Calculator.exe", pid);

        total += (GetTickCount64() - start);
    }

    std::wcout << L"Total: " << total << L"\tper call: " << total / 1000. << std::endl;

    return 0;

}

Total: 4094     per call: 4.094
Total: 4688     per call: 4.688

有比OpenProcesses+更快的方法GetProcessImageFileName吗?

我还发现QueryFullProcessImageName了可以稍微减少 find_process_1 时间的函数

UPD1:使用 NtQuerySystemInformation 代码的解决方案是错误的,请参阅解决方案

#include <winternl.h>
#pragma comment(lib,"ntdll.lib")
struct _SYSTEM_PROCESS_INFO
{
    ULONG                   NextEntryOffset;
    ULONG                   NumberOfThreads;
    LARGE_INTEGER           Reserved[3];
    LARGE_INTEGER           CreateTime;
    LARGE_INTEGER           UserTime;
    LARGE_INTEGER           KernelTime;
    UNICODE_STRING          ImageName;
    ULONG                   BasePriority;
    HANDLE                  ProcessId;
    HANDLE                  InheritedFromProcessId;
};

bool find_process_3(const std::wstring& name, DWORD& pid)
{
    _SYSTEM_PROCESS_INFO* spi;
    size_t size = 1024*1024;
    PVOID buffer = VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    ULONG real_size {0};

    NTSTATUS ret = NtQuerySystemInformation(SystemProcessInformation, buffer, size, &real_size);

    bool found {false};

    if (NT_SUCCESS(ret) == true)
    {
        spi = (_SYSTEM_PROCESS_INFO*)buffer;

        while(spi->NextEntryOffset)
        {
            if (spi->ImageName.Buffer != nullptr && _wcsicmp(spi->ImageName.Buffer, name.c_str()) == 0)
            {
                pid = (long)spi->ProcessId;
                found = true;
                break;
            }

            spi = (_SYSTEM_PROCESS_INFO*)((LPBYTE)spi + spi->NextEntryOffset);
        }
    }

    VirtualFree(buffer, 0, MEM_RELEASE);
    return found;
}

和结果:

Total: 4562     per call: 4.562 // OpenProcess + GetProcessImageFileName
Total: 4453     per call: 4.453 // OpenProcess + QueryFullProcessImageName
Total: 5188     per call: 5.188 // CreateToolhelp32Snapshot
Total: 2797     per call: 2.797 // NtQuerySystemInformation

看起来真的更快,谢谢@RbMm

4

1 回答 1

1

对于按名称获取进程ID,需要枚举进程并将其名称与给定的名称进行比较。我不听仅执行此操作(并且没有内部枚举)的准备好的系统 api 的最低限度。当然需要了解,这对于系统级调试工具来说存在意义主要 - 进程的名称不可靠。可以是多个同名的进程,等等。

最快的低级方法 - 使用NtQuerySystemInformation带有SystemProcessInformation信息类的函数。所有其他方式 - 基于此 api。但有大量开销和丢失信息。

CreateToolhelp32Snapshot- 内部调用NtQuerySystemInformation函数,但使用部分(win32 语言上的SystemProcessInformation文件映射)作为信息存储。复制此部分的数据并取消映射Process32First Process32Next- all time再次将部分映射到内存,将数据复制到缓冲区(并在此过程中删除一些数据),然后取消映射部分。所有这些严重的开销。当然,如果你只这样做一次 - 你不会看到不同的东西,但如果这样做很多次 - 不同的速度将是可见的。

EnumProcesses当然也可以从所有返回的信息中使用NtQuerySystemInformation函数SystemProcessInformation- 仅传递每个进程的进程标识符,并删除所有其他信息。结果,您需要调用OpenProcess并查询它的图像路径-再次造成严重开销,并且您无法打开说受保护的进程。

当然,这里我只描述了当前的实现。可能会改变。也许不吧。然而,这解释了为什么NtQuerySystemInformation最快。

所以记录它与否,“支持”或否 -NtQuerySystemInformation如果使用正确,这是最快的方法。

在 Windows 的未来版本中可能会更改或不可用

这已经写了20年了。但仍然是错误的。我个人确信这个 api 永远不会被更改或不可用(至少不会早于CreateToolhelp32Snapshot并且EnumProcesses也将被更改或不可用) - 这是基本系统 api 之一。没有任何理由这样做。

此函数没有关联的导入库。您必须使用 LoadLibrary 和 GetProcAddress 函数动态链接到 Ntdll.dll。

这也是谎言。甚至存在来自 wdk 的 2 个库 - ntdll.libntdllp.lib(这里有更多 api,但如果您使用 crt - 多个定义的符号,此库在某些情况下可能与 crt 冲突) - 所以我们可以,但不需要使用LoadLibraryGetProcAddress(非常有趣对我来说 - 我们如何在调用之前调用LoadLibraryGetProcAddress不调用LoadLibrary以及GetProcAddress获取和的地址LoadLibraryGetProcAddress

真正NtQuerySystemInformation常用的 api 函数,并像任何常用的 api 函数一样调用。我们需要的所有东西 - 编译器的声明和链接器的 lib 文件。lib 存在于 wdk 中(并且一直在这里),尽管 msdn 说另一个

使用示例

NTSTATUS GetProcessIdByName(PCUNICODE_STRING ImageName, HANDLE& UniqueProcessId)
{
    NTSTATUS status;

    ULONG cb = 0x10000;

    UniqueProcessId = 0;

    do 
    {
        status = STATUS_INSUFFICIENT_RESOURCES;

        if (PVOID buf = new UCHAR[cb])
        {
            if (0 <= (status = NtQuerySystemInformation(SystemProcessInformation, buf, cb, &cb)))
            {
                status = STATUS_NOT_FOUND;

                union {
                    PVOID pv;
                    PBYTE pb;
                    PSYSTEM_PROCESS_INFORMATION pspi;
                };

                pv = buf;
                ULONG NextEntryOffset = 0;

                do 
                {
                    pb += NextEntryOffset;

                    if (RtlEqualUnicodeString(ImageName, &pspi->ImageName, TRUE))
                    {
                        UniqueProcessId = pspi->UniqueProcessId;
                        status = STATUS_SUCCESS;
                        break;
                    }

                } while (NextEntryOffset = pspi->NextEntryOffset);

            }

            delete [] buf;
        }

    } while (status == STATUS_INFO_LENGTH_MISMATCH);

    return status;
}

请注意,因为所需的缓冲区大小非常不稳定(始终创建/退出新线程) - 需要在循环中调用此 api,直到我们没有STATUS_INFO_LENGTH_MISMATCH状态

注意do while (NextEntryOffset = pspi->NextEntryOffset)循环 - 如果做 while 循环 - 我们丢失了最后一个条目(系统中最新生成的进程)。和 ImageName - 这是UNICODE_STRING- 所以不是强制性的零终止。结果使用假设 0 终止字符串的字符串 api - 此处不正确(工作,因为在此结构中确实使用了 0 终止字符串)正确使用RtlEqualUnicodeString此处或类似

此代码也按名称搜索过程,直到找到第一个匹配名称。当然需要了解可以是同名的多个进程 - 例如svchost.exe。在实际搜索中,我们可以使用其他条件,如 sessionid、进程令牌属性、命令行等。这已经是单独的问题,取决于要求

于 2019-08-29T12:20:45.177 回答