Search code examples
c++windowswinapiprocessshutdown

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


Goal: Shut down a running 32 bit GUI process under windows

  • I have access to the executable pathname.
  • There are potentially more than one copy of this software running, but only one started from a unique executable pathname.
  • Because more than one instance of this executable can be running, a simple look at top level windows would need to distinguish between which executable pathname actually is responsible for that window...

Possible Approaches:

Enumerate processes & threads, then use PostThreadMessage(thread, WM_QUIT, 0, 0)

  • This makes sense, but I'm worried about what technique to use to distinguish "the main thread"

There are examples of such an approach:

Enumerate top level windows, obtain the process identity, and send the message to the window:

Other Thoughts:

  • My target application is multilingual - so looking at the name of the top level window seems incorrect as well... since I won't know what it will say (it is also dynamic according to the user's settings).

Basically, what I want is a sure-fire way to tell my application - the specific instance that is started from a specific executable pathname (arguments don't matter - but path does), to shut down.

Is there a better way entirely:

  • Maybe creating a named semaphore to signal?
  • Registered Windows message broadcast (with the pathname passed through as an ATOM)?
  • Some other IPC mechanism?

Thanks in advance for any thoughts you might offer...


Solution

  • 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).