Если MessageBox () / related является синхронным, почему мой цикл сообщений не останавливается? - PullRequest
12 голосов
/ 11 августа 2009

Почему, если я вызываю, казалось бы, синхронную функцию 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 ]

10 голосов
/ 11 августа 2009

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

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

2 голосов
/ 11 августа 2009

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, который вызовет любые обработчики сообщений.

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

...