4

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

Community
  • 1
  • 1
Mordachai
  • 9,412
  • 6
  • 60
  • 112
  • Assuming the other applications are not yours, I would post the `WM_QUIT` message not to a thread, but to an application's main window. Expect that the application knows how to release its own resources and shut down its own threads. – John Dibling May 20 '13 at 16:06
  • How would I detect what is the main window for a given process (I can get the process ID or handle trivially enough) – Mordachai May 20 '13 at 16:13
  • I found what looks like a good answer to that question here: http://stackoverflow.com/questions/1888863/how-to-get-main-window-handle-from-process-id – John Dibling May 20 '13 at 16:16
  • Unfortunately, that would give me two thread IDs in my case: one for the main window, and one for a sort of trace window (it creates both of them as root level windows, they both belong to the same process, but to different threads, still no way to distinguish which one is "main") – Mordachai May 20 '13 at 16:20
  • 4
    Just enumerate the windows of the target process and post `WM_QUIT` messages to all of them. Multiple windows running in the same thread will simply put redundant messages in the same queue, but it only takes 1 of them to end that thread's message loop. – Remy Lebeau May 20 '13 at 21:50
  • Indeed, @RemyLebeau, I was able to use essentially this technique: Find the process ID, find all top level windows belonging to that process, and send WM_QUIT to each thread which owned one of those top level windows for that process. This works beautifully. I'll post code in a bit... – Mordachai May 21 '13 at 16:46
  • @Mordachai: I wasn't suggesting you post to the thread directly, but to post to the individual windows themselves. – Remy Lebeau May 21 '13 at 17:36
  • Seems to me that you either send a WM_CLOSE to windows, or a WM_QUIT to threads. Doesn't really make a difference on WM_QUIT, I suspect. – Mordachai May 23 '13 at 13:37

1 Answers1

5

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

phimuemue
  • 34,669
  • 9
  • 84
  • 115
Mordachai
  • 9,412
  • 6
  • 60
  • 112