按名称查找进程 ID 的快速方法

Fast way to find process id by name

本文关键字:方法 ID 进程 查找      更新时间:2023-10-16

任务是:通过可执行文件名查找进程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

对于按名称获取进程 ID 需要枚举进程并将其名称与给定的名称进行比较。 我最不听 READY 系统 API,它只是这样做(并且没有枚举内部(。 当然需要了解,这是否存在 系统级调试工具的主要意义 - 进程名称不可靠。 可以是许多同名的进程, 等。

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

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

当然,EnumProcesses也使用SystemProcessInformationNtQuerySystemInformation函数,但从所有返回的信息中 - 只传递每个进程的进程标识符,并删除所有其他信息。 因此,您需要调用OpenProcess并查询其图像路径 - 再次严重的开销,您无法打开受保护的进程。

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

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

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

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

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

这也是谎言。 甚至存在来自 WDK 的 2 个库 - ntdll.lib 和ntdllp.lib(这里有更多的 api,但如果您使用 CRT - 多个定义的符号,这个库在某些情况下可能会与 CRT 冲突( - 所以我们可以,但不需要使用LoadLibraryGetProcAddress(对我来说非常有趣 - 我们如何在调用LoadLibraryGetProcAddress之前调用LoadLibraryGetProcAddress以获取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 循环 - 我们丢失了最后一个条目(系统中最新生成的进程(。 和图像名称 - 这是UNICODE_STRING- 所以不是强制性的零终止。 结果使用假设 0 终止字符串的字符串 API - 这里不正确(工作是因为在这个结构中确实使用了 0 终止的字符串( 正确使用RtlEqualUnicodeString这里或类似

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