1

I am trying to close a C# .NET 4 WPF application from a c++ application. The C++ app uses the standard technique of enumerating windows, finding the one that corresponds to a given process ID, sending the window a WM_CLOSE via PostMessage, then WaitForSingleObject(pid, 5000). However, my WPF app never closes, ie the WaitForSingleObject times out.

My WPF app overrides the Window::OnClosed():

  • If I manually close the WPF app by clicking on the X of the window, this method gets called.
  • Similarly, if in Windows' Task Manager in the Application tab I do "End Task" on the WPF process, this method gets called (apparently on that tab the WM_CLOSE message is used, whereas on the Processes tab the End Task uses a WM_QUIT message).
  • When my C++ app sends the WM_CLOSE, this method is never called
  • When my C++ app sends WM_QUIT instead (so all C++ source code unchanged except for the message sent), my WPF app is terminated.
  • I have tried creating my own WndProc() handler in the WPF app, and method does get called if I mouse over the WPF GUI, but again when my C++ app sends the WM_CLOSE, this method is never called, it's almost like my WPF app does not get the WM_CLOSE message.
  • I have created a C# app from where I can use Process.GetProcessById(pid) and proc.CloseMainWindow(), which is supposed to do the same as WM_CLOSE. This one works: the OnClosed() method gets called.

Is PostMessage(hwnd, WM_CLOSE) the right way to gracefully close a WPF app from a C++ app?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Oliver
  • 27,510
  • 9
  • 72
  • 103
  • I have meanwhile verified with Spy++ that WM_CLOSE does not reach my WPF app when the C++ app sends WM_CLOSE. – Oliver Mar 06 '13 at 05:05
  • Just bumped into the same problem. Wish there was a solution. – Kevin Gale Apr 15 '13 at 19:44
  • @KevinGale you might want to check the answer I provided to my own question, as I did eventually find an answer, maybe it will help you too! – Oliver Aug 12 '13 at 21:58

2 Answers2

2

I did find the answer eventually, and it was quite simple: the problem was the code related to "finding the one that corresponds to a given process ID". Issue is that I don't do much low level win32 stuff, so I missed an important detail. Here is what was happening and solution, in case it helps someone:

The C++ function closeProc() opens a handle to the existing process that must be closed, and causes a callback function requestMainWindowClose() to be called for each Window found by a win32 function EnumWindows, and assumes that requestMainWindowClose() has sent the close message to the process of interest, so it waits for the process to exit. If the process doesn't exit within a certain time, it will try to terminate it forcefully via TerminateProcess(). If that still doesn't work, it gives up. The closeProc() looks like this:

void closeProc()
{
    HANDLE ps = OpenProcess( SYNCHRONIZE | PROCESS_TERMINATE, FALSE, dwProcessId );
    if (ps == NULL)
        throw std::runtime_error(...);

    EnumWindows( requestMainWindowClose, dwProcessId );

    static const int MAX_WAIT_MILLISEC = 5000; 
    const DWORD result = WaitForSingleObject(ps, MAX_WAIT_MILLISEC);
    if (result != WAIT_OBJECT_0)
    {
        if (result == WAIT_TIMEOUT)
        {
            LOGF_ERROR("Could not clcose proc (PID %s): did not exit within %s ms",
                dwProcessId << MAX_WAIT_MILLISEC);
        }
        else
        {
            LOGF_ERROR("Could not close proc (PID %s): %s", 
                dwProcessId << getLastWin32Error());
        }

        LOGF_ERROR("Trying to *terminate* proc (PID %s)", dwProcessId);
        if (TerminateProcess(ps, 0))
            exited = true;
        }
    }

    CloseHandle( ps ) ;
}

The problem was in requestMainWindowClose, here was the original code:

BOOL CALLBACK 
requestMainWindowClose( HWND nextWindow, LPARAM closePid )
{
    DWORD windowPid;
    GetWindowThreadProcessId(nextWindow, &windowPid);
    if ( windowPid==(DWORD)closePid )
    {
        ::PostMessage( nextWindow, WM_CLOSE, 0, 0 );
        return false;
    }

    return true;
}

As defined above the callback function determines the process ID of the Window handle given to it (nextWindow) by EnumWindows() and compares to the desired process we want to close (closePid). If there is a match, the function sends it a CLOSE message and returns.

All is good so far. The problem is that it returns false, so EnumWindows() only ever sends the message to one window of the process, AND it looks like WPF applications have multiple windows: even if your code only creates one window, hidden windows get created behind the scenes by WPF. They are all found by EnumWindows; but the first one is rarely if ever the main application window. So requestMainWindowClose() was never sending the CLOSE to the main window of my WPF app, never got a chance.

Indeed the fix was that simple, ie don't return false:

BOOL CALLBACK 
requestMainWindowClose( HWND nextWindow, LPARAM closePid )
{
    DWORD windowPid;
    GetWindowThreadProcessId( nextWindow, &windowPid );
    if ( windowPid==(DWORD)closePid )  
        ::PostMessage( nextWindow, WM_CLOSE, 0, 0 );

    return true; 
}

Only the top app window of a WPF will respond to the CLOSE message.

Oliver
  • 27,510
  • 9
  • 72
  • 103
1

The direct equivalent of the Close command on the system menu is:

PostMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);

You could try that instead.

RichieHindle
  • 272,464
  • 47
  • 358
  • 399
  • This should work too and it's a good idea to try, but it's not clear from your answer if there are any situations where PostMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0) would work and PostMessage(hwnd, WM_CLOSE, 0, 0) would not. There was no difference, that one fails too. – Oliver Mar 06 '13 at 05:04