如果 MessageBox()/相关是同步的,为什么我的消息循环没有冻结?

发布于 2024-08-02 02:09:22 字数 1819 浏览 3 评论 0原文

为什么如果我在消息循环内调用看似同步的 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() 做什么来允许此功能? 系统是否“复制”我的应用程序线程,这样一旦 MessageBox() 完成,它就可以完成 WM_LBUTTONDOWN 代码,同时仍然允许原始线程处理其他消息在过渡期? (这是我未经教育的猜测)

提前致谢

Why is it that if I call a seemingly synchronous Windows function like MessageBox() inside of my message loop, the loop itself doesn't freeze as if I called Sleep() (or a similar function) instead? To illustrate my point, take the following skeletal 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;
}

In the above example, the program's main function is to run a timer and display the counter's value every second. However, if the user clicks on our window, the program displays a message box and then beeps after the box is closed.

Here's where it gets interesting: we can tell MessageBox() is a synchronous function because MessageBeep() doesn't execute until the message box is closed. However, the timer keeps running, and the window is repainted every second even while the message box is displayed. So while MessageBox() is apparently a blocking function call, other messages (WM_TIMER/WM_PAINT) can still be processed. That's fine, except if I substitute MessageBox for another blocking call like Sleep()

    case WM_LBUTTONDOWN:
         Sleep(10000); //wait 10 seconds
         MessageBeep(MB_OK);
         break;

This blocks my application entirely, and no message processing occurs for the 10 seconds (WM_TIMER/WM_PAINT aren't processed, the counter doesn't update, program 'freezes', etc). So why is it that MessageBox() allows message processing to continue while Sleep() doesn't? Given that my application is single-threaded, what is it that MessageBox() does to allow this functionality? Does the system 'replicate' my application thread, so that way it can finish the WM_LBUTTONDOWN code once MessageBox() is done, while still allowing the original thread to process other messages in the interim? (that was my uneducated guess)

Thanks in advance

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

千笙结 2024-08-09 02:09:22

MessageBox() 和类似的 Windows API 函数不会像 IO 操作或互斥那样阻塞执行。 MessageBox() 函数创建一个通常带有“确定”按钮的对话框 - 因此您希望自动处理与该消息框相关的窗口消息。 这是通过其自己的消息循环实现的:不会创建新线程,但您的应用程序仍保持响应,因为选定的消息(例如绘画)是通过递归调用您的 WndProc() 函数来处理的,而其他消息则由由于所创建窗口的模态类型,未传输。

Sleep() 和其他函数(当直接从处理窗口消息的 WndProc() 调用时)实际上会阻止单线程消息循环的执行 - 没有其他消息会阻塞被处理。

The MessageBox() and similar Windows API functions are not blocking the execution, like an IO operation or mutexing would do. The MessageBox() function creates a dialog box usually with an OK button - so you'd expect automatic handling of the window messages related to the message box. This is implemented with its own message loop: no new thread is created, but your application remains responsive, because selected messages (like for painting) are handled calling recursively your WndProc() function, while other messages are not transmitted, because of the modal type of the created window.

Sleep() and other functions (when called directly from your WndProc() handling a window message) would actually block the execution of your single threaded message loop - no other message would be processed.

许仙没带伞 2024-08-09 02:09:22

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 将调用它需要的任何窗口过程。 该窗口过程可以启动自己的循环(在同一线程上),并且它将调用 DispatchMessage 本身,这将调用任何消息处理程序。

如果您想查看它,请在调试器中启动您的应用程序,弹出消息框并中断。 你将被丢弃在它的循环中的某个地方。 查看调用堆栈,看看是否可以找到父循环。

MessageBox runs its own Win32 message loop (so as not to freeze calling app).

Beware of using it in non reentrant functions...

EDIT: to elaborate:
Message loop on windows is something like that (stolen from msdn):

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
} 

DispatchMessage will call whatever window procedure it needs to. That window proc can start its own loop (on the same thread), and it will call DispatchMessage itself, which will call whatever message handlers.

If you want to see it, launch your app in debugger, pop up message box and break. You will be dropped somewhere within its loop. Look at the callstack and see if you can find parent loop.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文