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

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

2 ответа

Решение

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

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

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 будет вызывать любую оконную процедуру, в которой она нуждается. Этот оконный процесс может запустить свой собственный цикл (в том же потоке) и сам вызовет DispatchMessage, который будет вызывать любые обработчики сообщений.

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

Другие вопросы по тегам