Если MessageBox()/related являются синхронными, почему мой цикл сообщений не замерзает?

Почему это так, что если я вызову, по-видимому, синхронную функцию 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(), но при этом разрешая исходному потоку обрабатывать другие сообщения в промежуточный период? (это была моя необразованная догадка)

Заранее спасибо

Ответ 1

MessageBox() и аналогичные функции Windows API не блокируют выполнение, например, операция ввода-вывода или мьютексинг. Функция MessageBox() создает диалоговое окно, обычно с кнопкой OK, поэтому вы ожидаете автоматической обработки сообщений Windows, связанных с полем сообщений. Это реализовано с помощью собственного цикла сообщений - новый поток не создается, но ваше приложение остается отзывчивым, потому что выбранные сообщения, такие как Paint, обрабатываются, вызывая рекурсивно вашу функцию WndProc(), а некоторые сообщения не передаются из-за модального типа созданное окно.

Сон() и другие функции, вызываемые непосредственно из вашего WndProc(), обрабатывающего сообщение Windows, фактически блокируют выполнение одного потока с потоковым сообщением, никакое другое сообщение не будет обрабатываться.

Ответ 2

MessageBox запускает собственный цикл сообщений Win32 (чтобы не замораживать вызывающее приложение).

Остерегайтесь использовать его в невозвратных функциях...

РЕДАКТИРОВАТЬ: разработать: Цикл сообщений в окнах - это нечто подобное (украденное из 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, который будет вызывать любые обработчики сообщений.

Если вы хотите его увидеть, запустите приложение в отладчике, всплывающее окно сообщения и перерыв. Вы попадете куда-нибудь в свой цикл. Посмотрите на столбец и посмотрите, можете ли вы найти родительский цикл.