【问题标题】:WM_QUIT only posts for thread and not the window?WM_QUIT 只发布线程而不是窗口?
【发布时间】:2015-12-22 12:05:20
【问题描述】:

在 Windows API 中,我正在研究 GetMessage 函数的实际工作原理。我已经看到了 3 个 Windows 消息循环的实现,并想探索它们。


1)

截至撰写本文时,this MSDN article 描述了我认为实现消息循环的正确方式。

MSG msg;
BOOL bRet;
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
} 


2)

GetMessage function page,我看到了这个实现:

MSG msg;
BOOL bRet;
while( (bRet = GetMessage( &msg, hWnd, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
}


3)

最后,Visual Studio documentation 将此实现作为其 Win32 应用程序演示的一部分。

MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}


讨论

简而言之,实现#3 忽略从GetMessage 返回的错误,但在其他方面与第一个实现相同。也就是说,它们都处理当前线程的所有消息。当GetMessage 函数返回0 时,循环终止。

因为我在 #1 之前找到了实现 #2,所以我认为它已经完成了。但是,我注意到当通过PostQuitMessage 发布WM_QUIT 消息时,GetMessage 不会返回0

这导致了一些混乱,直到我找到了实现 #1 并对其进行了测试。前两个实现之间的区别在于GetMessage 的第二个参数。在#2 中,它指定了hWnd,根据GetMessage 文档,它是:

要检索其消息的窗口句柄。该窗口必须属于当前线程。

在#1 中,它是NULL,与此摘录有关:

如果 hWnd 为 NULL,GetMessage 检索属于当前线程的任何窗口的消息,以及当前线程的消息队列中 hwnd 值为 NULL 的任何消息(参见 MSG 结构)。因此如果 hWnd 为 NULL,窗口消息和线程消息都会被处理。

使用NULL进行测试时,GetMessage函数在处理WM_QUIT消息时返回0,成功终止循环。

问题

  1. 即使PostQuitMessage 是从特定窗口的回调函数中调用的,WM_QUIT 实际上属于窗口还是当前线程?根据对这三个实现的测试,它似乎与当前线程相关联。

  2. 如果与线程关联,何时使用有效的hWnd 作为GetMessage 的参数是有用或合适的?这样的消息循环将无法返回0 作为对WM_QUIT 的反应,那么消息循环是否应该终止另一种方式?

参考文献

代码

#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch(msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int nCmdShow) {
    LPCTSTR wndClassName =_T("Class_SHTEST");
    LPCTSTR wndName = _T("SHTest");

    WNDCLASSEX wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW|CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
    wcex.hCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
    wcex.hbrBackground = (HBRUSH) COLOR_WINDOW+1;
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = wndClassName;
    wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));

    if (!RegisterClassEx(&wcex))
    {
        MessageBox(NULL, _T("Call to RegisterClassEx failed!"), wndName, MB_OK|MB_ICONERROR);
    }

    HWND window = CreateWindow(wndClassName, wndName,
        WS_OVERLAPPEDWINDOW | WS_MAXIMIZE, 
        0, 0, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);
    if (!window) {
        MessageBox(NULL, _T("Call to CreateWindow failed!"), wndName, MB_OK|MB_ICONERROR);
    }

    ShowWindow(window, SW_SHOW);
    UpdateWindow(window);

    //Message loop (using implementation #1)
    MSG msg;
    BOOL bRet;
    while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            //Handle error and possibly exit.
        }
        else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    //Return the exit code in the WM_QUIT message.
    return (int) msg.wParam;
}

【问题讨论】:

  • 嗯,每个人都知道 BOOL 可以是 FALSE、TRUE 或 FileNotFound。如果你得到-1,那么你做错了,没有什么可以处理的。当你使用 PostQuitMessage() 时没有从 GetMessage() 得到 0 也是错误的。您需要发布重现代码。或者使用库,今天编写可靠的 Win32 代码是火箭科学,应该留给必须支持它的人。
  • @HansPassant 您能否详细说明是什么导致这些场景出错?我已经继续并在我的帖子中附加了一些代码。
  • 使用#3。由于您为窗口句柄传递 NULL 和有效的 MSG 变量,因此 GetMessage 不能返回 -1。阅读 Raymond Chen 关于该主题的文章。
  • When will GetMessage return -1?。 Raymond Chen 的博客也有几篇关于WM_QUITPostQuitMessage() 的操作方式,以及如何在消息循环中正确使用它们的文章。
  • 有趣。这可以解释为什么当我传递一个 hWnd 并尝试关闭该窗口时,我收到 -1 作为返回值。这应该意味着我只需要在不使用完全未过滤的消息泵时检查 -1(即不是GetMessage(&amp;msg, NULL, 0, 0)

标签: c++ windows winapi message-queue


【解决方案1】:

根据WM_QUITMSDN documention

WM_QUIT 消息与窗口无关,因此将 永远不会通过窗口的窗口过程接收。它被检索 只能通过 GetMessage 或 PeekMessage 函数。

由于WM_QUIT 与窗口无关,并且将HWND 传递给GetMessage() 只会检索与该窗口关联的那些消息,因此后者将永远不会收到WM_QUIT 的设计。

至于您何时希望将HWND 传递给GetMessage(),在您不会的应用程序的一般消息循环中。但有时您希望在 UI 中发生某些事情时发送消息,并且只关心与特定窗口相关联的消息。

【讨论】:

    【解决方案2】:

    WM_QUIT 与线程相关,而不是与单个窗口相关。请注意PostQuitMessage() 缺少hwnd 参数。它不可能是特定于窗口的,因为无法告诉它为哪个窗口生成消息。

    WM_QUIT 实际上并不是真正的消息。当您调用PostQuitMessage() 时,在已请求WM_QUIT 的消息队列状态中设置了一个内部标志。这将由GetMessage()PeekMessage() 在未来的某个时间自动生成(通常是立即生成,但如果队列中包含其他已发布的消息,则会首先处理这些消息)。

    Raymond Chen's blog 对此进行了更详细的解释,其中还包含以下引用:

    作为另一种特殊行为,生成的 WM_QUIT 消息绕过 传递给 GetMessage 和 PeekMessage 的消息过滤器 职能。如果设置了内部“退出消息挂起”标志,则 一旦队列安静,您将收到一条 WM_QUIT 消息,无论 你通过什么过滤器。

    这表明您的观察结果是错误的,即使提供了过滤器参数,上面示例 #2 中的 GetMessage() 也应在调用 PostQuitMessage() 后返回 0。

    一般来说,您应该只在您有特定需求时才使用消息过滤器(例如,您特别想检索发布到特定窗口的消息)。在大多数情况下,这些参数都应设置为 0 以使您的 UI 正常运行。

    【讨论】:

    • 有趣的是,WM_QUIT 不是一条正常的消息。关于您的报价,我相信提到的过滤器是Getmessage(wMsgFilterMin 和 wMsgFilterMax)的最后两个参数指定的。在我看来,HWND 参数不应包含在该过滤器中,即使它限制了检索哪些消息。不幸的是,我没有可靠的方法来测试过滤。在我对实现#2 的测试中,我记录了GetMessage 的所有返回值,而0 没有出现一次。这与 WM_QUIT 与线程相关是一致的。
    • @NickMiller 我同意这并不完全清楚,尽管所有三个参数都可以被视为过滤器,因为它们控制函数返回的消息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多