Task is: find process id by executable name.
Calling application is 32 bit, finding process can be 32 or 64 bit
Solution:
#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
Is there way faster than OpenProcesses
+ GetProcessImageFileName
?
Also i found QueryFullProcessImageName
function that can slightly decrease find_process_1 time
UPD1: solution with NtQuerySystemInformation Code is wrong see solution
#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;
}
and results:
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
looks really faster, thanks @RbMm
for get process id by name need enumerate processes and compare it names with given. how minimum i not listen about ready system api which just do this (and without enumeration internal). of course need understand that do this exist sense primary for system level debug tools - the name of process not reliable. can be many processes with same name, etc.
the fastest low-level way do this - use NtQuerySystemInformation
function with SystemProcessInformation
information class. all other ways - based on this api. but have significant overhead and losing info.
the CreateToolhelp32Snapshot
- internal call NtQuerySystemInformation
function with SystemProcessInformation
but use section ( file mapping on win32 language) as storage for information. copy data tho this section and unmap it. the Process32First
Process32Next
- all time map section to memory again, copy data to your buffer (and drop some data in this process) and then unmap section. all this serious overhead. of course if you only once do this - you not view different, but if do this many time - different in speed will be visible.
the EnumProcesses
of course also use NtQuerySystemInformation
function with SystemProcessInformation
but from all returned information - pass only process identifier for each process, and drop all another info. as result you need call OpenProcess
and query it image path - again serious overhead and you can not open say protected processes.
of course here i described only current implementation. may be it will changed. may be not. however this is explain why NtQuerySystemInformation
is fastest.
so documented it or no, "supported" or no - the NtQuerySystemInformation
is fastest way, if use it correct.
may be altered or unavailable in future versions of Windows
this is written already 20 years. but still false. i personally sure that this api never will be altered or unavailable (how minimum not early than CreateToolhelp32Snapshot
and EnumProcesses
also will be altered or unavailable) - this is one of fundamental system api. no any reason do this.
This function has no associated import library. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Ntdll.dll.
this is also lie. exist even 2 lib from wdk - ntdll.lib and ntdllp.lib (here more api but this lib can conflict with crt in some case if you use crt - multiple defined symbols) - so we can, but not need use LoadLibrary
and GetProcAddress
(very interesting for me - how we can call LoadLibrary
and GetProcAddress
without before call LoadLibrary
and GetProcAddress
for get addresses of LoadLibrary
and GetProcAddress
).
really NtQuerySystemInformation
usual api function and called as any usual api function. all what we need - declaration for compiler and lib file for linker. lib exist in wdk (and always was here), despite msdn say another
example of usage
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;
}
note that because required buffer size is very volatile (all time new threads created/exited) - need cal this api in loop until we got not STATUS_INFO_LENGTH_MISMATCH
status
note do while (NextEntryOffset = pspi->NextEntryOffset)
loop - if do while loop - we lost last entry (latest spawned process in system). and ImageName - this is UNICODE_STRING
- so not mandatory zero terminated. as result use string api which assume 0 terminated string - not correct here (work because in this struct really used 0 terminated string) correct use RtlEqualUnicodeString
here or similar
also this code search process by name until first match name found. of course need understand that can be multiple processes with same name - for example svchost.exe. in real search we can possible use and another conditions, like sessionid, process token properties, command line, etc. this already is separate question and depend from requirements