c++winapint-native-api

Fast way to find process id by name


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


Solution

  • 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