How (best) to post WM_QUIT to a running process?

痞子三分冷 提交于 2019-12-04 19:18:45

Here's how I solved it for myself, compatible with XP, and can deal with a process that has multiple top-level windows and multiple threads, assuming that the target process does correctly handle WM_QUIT for itself (which it certainly should!)

I'm targeting Win32 API from C++:

call Shutdown(filename); That calls GetProcessID(filename) to get the process ID And then calls EnumerateWindowThreads(processID) in order to get the set of threads with top level windows (which we can assume are 'main' threads for the process), and uses PostThreadMessage(..., WM_QUIT, ...) to ask each of them to terminate.

You can open a process handle on the process ID before posting the WM_QUIT messages if you want to call GetExitCodeProcess(process_handle, &exit_code). Just make sure you obtain and hold open a process handle before/while you're posting the quits in order to ensure you have something to query after it is done...

DWORD Shutdown(const TCHAR * executable)
{
    // assumption: zero id == not currently running...
    if (DWORD dwProcessID = GetProcessID(executable))
    {
        for (DWORD dwThreadID : EnumerateWindowThreads(dwProcessID))
            VERIFY(PostThreadMessage(dwThreadID, WM_QUIT, 0, 0));
    }
}

// retrieves the (first) process ID of the given executable (or zero if not found)
DWORD GetProcessID(const TCHAR * pszExePathName)
{
    // attempt to create a snapshot of the currently running processes
    Toolbox::AutoHandle::AutoCloseFile snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
    if (!snapshot)
        throw CWin32APIErrorException(_T(__FUNCTION__), _T("CreateToolhelp32Snapshot"));

    PROCESSENTRY32 entry = { sizeof(PROCESSENTRY32), 0 };
    for (BOOL bContinue = Process32First(snapshot, &entry); bContinue; bContinue = Process32Next(snapshot, &entry))
    {
#if (_WIN32_WINNT >= 0x0600)
        static const BOOL isWow64 = IsWow64();
        if (isWow64)
        {
            Toolbox::AutoHandle::AutoCloseHandle hProcess(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID));
            DWORD dwSize = countof(entry.szExeFile);
            if (!QueryFullProcessImageName(hProcess, 0, entry.szExeFile, dwSize))
                //throw CWin32APIErrorException(_T(__FUNCTION__), _T("QueryFullProcessImageName"));
                    continue;
        }
#else
        // since we require elevation, go ahead and try to read what we need directly out of the process' virtual memory
        if (auto hProcess = Toolbox::AutoHandle::AutoCloseHandle(OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, entry.th32ProcessID)))
        {
            if (!GetModuleFileNameEx(hProcess, nullptr, entry.szExeFile, countof(entry.szExeFile)))
                //throw CWin32APIErrorException(_T(__FUNCTION__), _T("GetModuleFileNameEx"));
                    continue;
        }
#endif
        if (compare_no_case(entry.szExeFile, pszExePathName) == STRCMP_EQUAL)
            return entry.th32ProcessID; // FOUND
    }

    return 0; // NOT FOUND
}


// returns the set of threads that have top level windows for the given process
std::set<DWORD> EnumerateWindowThreads(DWORD dwProcessID)
{
    if (!dwProcessID)
        throw CLabeledException(_T(__FUNCTION__) _T(" invalid process id (0)"));
    std::set<DWORD> threads;
    for (HWND hwnd = GetTopWindow(NULL); hwnd; hwnd = ::GetNextWindow(hwnd, GW_HWNDNEXT))
    {
        DWORD dwWindowProcessID;
        DWORD dwThreadID = ::GetWindowThreadProcessId(hwnd, &dwWindowProcessID);
        if (dwWindowProcessID == dwProcessID)
            threads.emplace(dwThreadID);
    }
    return threads;
}

My apologies for using Toolbox::AutoHandle::AutoCloseHandle and my various exception classes. They're trivial - AutoCloseHandle is RAII for HANDLE, and the exception classes exist because our code base predates the standard library (and the standard library still can't deal with UNICODE exceptions anyway).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!