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

时间:2015-09-24 19:00:15

标签: c++ windows winapi message-queue

在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,我认为它已经完成了。但是,我发现当GetMessage消息通过PostQuitMessage发布时,0不会返回WM_QUIT

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

  

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

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

  

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

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

问题

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

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

  3. 参考

    代码

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

2 个答案:

答案 0 :(得分:5)

根据WM_QUIT的{​​{3}}:

  

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

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

至于您希望将HWND传递给GetMessage()的时间,在应用程序的常规消息循环中,您不会。但是有时候你想在UI中的某些东西发生时抽取消息,并且只关注与特定窗口相关的消息。

答案 1 :(得分:4)

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

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

这在Raymond Chen's blog上有更详细的解释,其中还包含以下引用:

  

作为另一种特殊行为,生成的WM_QUIT消息会绕过   传递给GetMessage和PeekMessage的消息过滤器   功能。如果内部&#34;退出消息等待&#34;然后设置标志   无论如何,一旦队列安静,你将收到WM_QUIT消息   您通过的过滤器。

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

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