为什么如果我在消息循环中调用看似同步的Windows函数(如MessageBox()
),循环本身就不会像我调用Sleep()
(或类似函数)那样冻结?为了说明我的观点,请采用以下骨架WndProc
:
int counter = 0;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
SetTimer(hwnd, 1, 1000, NULL); //start a 1 second timer
break;
case WM_PAINT:
// paint/display counter variable onto window
break;
case WM_TIMER: //occurs every second
counter++;
InvalidateRect(hwnd, NULL, TRUE); //force window to repaint itself
break;
case WM_LBUTTONDOWN: //someone clicks the window
MessageBox(hwnd, "", "", 0);
MessageBeep(MB_OK); //play a sound after MessageBox returns
break;
//default ....
}
return 0;
}
在上面的例子中,程序的主要功能是运行计时器并每秒显示计数器的值。但是,如果用户单击我们的窗口,程序将显示一个消息框,然后在关闭该框后发出蜂鸣声。
这里有趣的地方:我们可以告诉MessageBox()
是一个同步函数,因为MessageBeep()
在消息框关闭之前不会执行。但是,计时器保持运行,即使显示消息框,窗口也会每秒重新绘制一次。因此,虽然MessageBox()
显然是阻止函数调用,但仍可以处理其他消息(WM_TIMER
/ WM_PAINT
)。这很好,除非我将MessageBox替换为Sleep()
case WM_LBUTTONDOWN:
Sleep(10000); //wait 10 seconds
MessageBeep(MB_OK);
break;
这完全阻止了我的应用程序,并且10秒内没有消息处理(WM_TIMER
/ WM_PAINT
未处理,计数器不更新,程序'冻结'等)。那么MessageBox()
为什么Sleep()
允许继续进行邮件处理呢?鉴于我的应用程序是单线程的,MessageBox()
允许此功能的是什么?系统是否“复制”我的应用程序线程,这样它可以在完成WM_LBUTTONDOWN
后完成MessageBox()
代码,同时仍允许原始线程在临时处理其他消息? (这是我没有受过教育的猜测)
提前致谢
答案 0 :(得分:10)
MessageBox()
和类似的Windows API函数不会阻止执行,就像IO操作或mutexing那样。 MessageBox()
函数创建一个通常带有OK按钮的对话框 - 因此您希望自动处理与消息框相关的Windows消息。这是通过它自己的消息循环实现的 - 没有创建新的线程,但是你的应用程序保持响应,因为像Paint这样的选定消息处理递归调用你的WndProc()
函数,并且由于模态,一些消息不会传输到已创建窗口的类型。
Sleep()和其他函数直接从处理Windows消息的WndProc()
调用,实际上会阻止单线程消息循环的执行,不会处理其他消息。
答案 1 :(得分:2)
MessageBox运行自己的Win32消息循环(以便不冻结调用应用程序)。
小心在非重入函数中使用它......
编辑:详细说明: Windows上的消息循环就是这样的(从msdn中窃取):
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
DispatchMessage将调用它需要的任何窗口过程。那个窗口proc可以启动它自己的循环(在同一个线程上),它将调用DispatchMessage本身,它将调用任何消息处理程序。
如果您想看到它,请在调试器中启动您的应用,弹出消息框并中断。你将被丢弃在它的循环中的某个地方。查看callstack,看看是否可以找到父循环。