如果MessageBox()/ related是同步的,为什么我的消息循环不会冻结?

时间:2009-08-10 20:14:41

标签: c++ winapi multithreading blocking synchronous

为什么如果我在消息循环中调用看似同步的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()代码,同时仍允许原始线程在临时处理其他消息? (这是我没有受过教育的猜测)

提前致谢

2 个答案:

答案 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,看看是否可以找到父循环。