【发布时间】: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,成功终止循环。
问题
即使
PostQuitMessage是从特定窗口的回调函数中调用的,WM_QUIT实际上属于窗口还是当前线程?根据对这三个实现的测试,它似乎与当前线程相关联。如果与线程关联,何时使用有效的
hWnd作为GetMessage的参数是有用或合适的?这样的消息循环将无法返回0作为对WM_QUIT的反应,那么消息循环是否应该终止另一种方式?
参考文献
GetMessagePostQuitMessage- Message Loop #1(Wikipedia Link)
- Message Loop #2
- Message Loop #3
WM_QUITMessage- Related Question
代码
#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_QUIT和PostQuitMessage()的操作方式,以及如何在消息循环中正确使用它们的文章。 -
有趣。这可以解释为什么当我传递一个 hWnd 并尝试关闭该窗口时,我收到 -1 作为返回值。这应该意味着我只需要在不使用完全未过滤的消息泵时检查 -1(即不是
GetMessage(&msg, NULL, 0, 0))
标签: c++ windows winapi message-queue