1

我正在尝试从 c++ 应用程序关闭 C# .NET 4 WPF 应用程序。C++ 应用程序使用枚举窗口的标准技术,找到与给定进程 ID 对应的窗口,通过 PostMessage 向窗口发送 WM_CLOSE,然后是 WaitForSingleObject(pid, 5000)。但是,我的 WPF 应用程序永远不会关闭,即 WaitForSingleObject 超时。

我的 WPF 应用程序覆盖了 Window::OnClosed():

  • 如果我通过单击窗口的 X 手动关闭 WPF 应用程序,则会调用此方法。
  • 同样,如果在 Windows 的“应用程序”选项卡中的任务管理器中,我在 WPF 进程上执行“结束任务”,则会调用此方法(显然在该选项卡上使用 WM_CLOSE 消息,而在“进程”选项卡上,结束任务使用 WM_QUIT 消息)。
  • 当我的 C++ 应用程序发送 WM_CLOSE 时,永远不会调用此方法
  • 当我的 C++ 应用程序改为发送 WM_QUIT 时(因此所有 C++ 源代码都未更改,除了发送的消息),我的 WPF 应用程序被终止。
  • 我尝试在 WPF 应用程序中创建自己的 WndProc() 处理程序,如果我将鼠标悬停在 WPF GUI 上,方法会被调用,但是当我的 C++ 应用程序再次发送 WM_CLOSE 时,这个方法永远不会被调用,它几乎就像我的 WPF 应用程序没有收到 WM_CLOSE 消息。
  • 我创建了一个 C# 应用程序,从中可以使用 Process.GetProcessById(pid) 和 proc.CloseMainWindow(),这应该与 WM_CLOSE 相同。这个可行: OnClosed() 方法被调用。

PostMessage(hwnd, WM_CLOSE) 是从 C++ 应用程序优雅地关闭 WPF 应用程序的正确方法吗?

4

2 回答 2

2

我最终确实找到了答案,而且非常简单:问题是与“查找与给定进程 ID 对应的那个”相关的代码。问题是我没有做太多低级win32的东西,所以我错过了一个重要的细节。这是正在发生的事情和解决方案,以防它帮助某人:

C++ 函数 closeProc() 打开必须关闭的现有进程的句柄,并为 win32 函数 EnumWindows 找到的每个窗口调用回调函数 requestMainWindowClose(),并假定 requestMainWindowClose() 已发送关闭消息到感兴趣的进程,所以它等待进程退出。如果进程在一定时间内没有退出,它将尝试通过 TerminateProcess() 强制终止它。如果还是不行,那就放弃。closeProc() 看起来像这样:

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 ) ;
}

问题出在 requestMainWindowClose 中,这里是原始代码:

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;
}

如上所述,回调函数确定 EnumWindows() 赋予它的窗口句柄 (nextWindow) 的进程 ID,并与我们想要关闭的所需进程 (closePid) 进行比较。如果匹配,该函数向它发送一个 CLOSE 消息并返回。

到目前为止一切都很好。问题是它返回 false,因此 EnumWindows() 只会将消息发送到进程的一个窗口,并且看起来 WPF 应用程序有多个窗口:即使您的代码只创建一个窗口,隐藏的窗口也会在幕后创建通过 WPF。它们都被 EnumWindows 发现;但第一个很少是主应用程序窗口。所以 requestMainWindowClose() 从来没有将 CLOSE 发送到我的 WPF 应用程序的主窗口,从来没有机会。

事实上,修复就是这么简单,即不要返回 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.

于 2013-04-17T03:07:46.463 回答
1

系统菜单上的关闭命令的直接等效项是:

PostMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);

你可以试试那个。

于 2013-03-06T00:44:13.900 回答