简体   繁体   中英

WPF app does not respond to WM_CLOSE

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?

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

PostMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);

You could try that instead.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM