WM_QUIT只发布线程而不是窗口?

Nic*_*ler 8 c++ windows winapi message-queue

在Windows API中,我正在研究GetMessage函数的实际工作方式.我已经看到了3个Windows消息循环的实现,并希望探索它们.


1)

截至撰写本文时,这篇MSDN文章描述了我认为实现消息循环的正确方法.

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); 
    }
} 
Run Code Online (Sandbox Code Playgroud)


2)

GetMessage功能页面上,我看到了这个实现:

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); 
    }
}
Run Code Online (Sandbox Code Playgroud)


3)

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

MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
Run Code Online (Sandbox Code Playgroud)


讨论

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

由于我在#1之前发现了#2,我认为它已经完成了.但是,我注意到通过发布消息时GetMessage不会返回0WM_QUITPostQuitMessage

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

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

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

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

使用时NULL,GetMessage函数会0WM_QUIT处理消息时返回,成功终止循环.

问题

  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;
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*ter 5

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

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

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

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

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

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


And*_*ndy 5

MSDN机制的文档WM_QUIT:

WM_QUIT消息与窗口无关,因此永远不会通过窗口的窗口过程接收.它仅由GetMessage或PeekMessage函数检索.

由于WM_QUIT未与窗口关联,并且传递HWNDGetMessage()仅检索与该窗口关联的那些消息,后者将永远不会WM_QUIT被设计接收.

至于你想要传递HWND给什么时候GetMessage(),在一个应用程序的一般消息循环中,你不会.但有时候,当UI中的某些内容发生时,您希望抽取消息,并且只关注与特定窗口关联的消息.