Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "WinAPI";
Текущий архив: 2003.08.21;
Скачать: [xml.tar.bz2];

Вниз

TimedMessageBox   Найти похожие ветки 

 
nikkie   (2003-06-15 00:50) [0]

Есть функция TimedMessageBox, взятая из MSDN Knowledge Base (ID: Q181934). Цель показать messagebox, который по истечении определенного времени закроется сам. Вот собственно сам код:
void CALLBACK
MessageBoxTimer(HWND hwnd, UINT uiMsg, UINT idEvent, DWORD dwTime)
{
PostQuitMessage(0);
}

UINT
TimedMessageBox(
HWND hwndParent,
LPCTSTR ptszMessage,
LPCTSTR ptszTitle,
UINT flags,
DWORD dwTimeout)
{
UINT idTimer;
UINT uiResult;
MSG msg;

/*
* Set a timer to dismiss the message box.
*/
idTimer = SetTimer(NULL, 0, dwTimeout, (TIMERPROC)MessageBoxTimer);

uiResult = MessageBox(hwndParent, ptszMessage, ptszTitle, flags);

/*
* Finished with the timer.
*/
KillTimer(NULL, idTimer);

/*
* See if there is a WM_QUIT message in the queue. If so,
* then you timed out. Eat the message so you don"t quit the
* entire application.
*/
( PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE)
Есть функция TimedMessageBox, взятая из MSDN Knowledge Base (ID: Q181934). Цель показать messagebox, который по истечении определенного времени закроется сам. Вот собственно сам код:
void CALLBACK
MessageBoxTimer(HWND hwnd, UINT uiMsg, UINT idEvent, DWORD dwTime)
{
PostQuitMessage(0);
}

UINT
TimedMessageBox(
HWND hwndParent,
LPCTSTR ptszMessage,
LPCTSTR ptszTitle,
UINT flags,
DWORD dwTimeout)
{
UINT idTimer;
UINT uiResult;
MSG msg;

/*
* Set a timer to dismiss the message box.
*/
idTimer = SetTimer(NULL, 0, dwTimeout, (TIMERPROC)MessageBoxTimer);

uiResult = MessageBox(hwndParent, ptszMessage, ptszTitle, flags);

/*
* Finished with the timer.
*/
KillTimer(NULL, idTimer);

/*
* See if there is a WM_QUIT message in the queue. If so,
* then you timed out. Eat the message so you don"t quit the
* entire application.
*/
if (PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE)) {

/*
* If you timed out, then return zero.
*/
uiResult = 0;
}

return uiResult;
}


Проблема: вызов функции в некоторой специфической ситуации приводит к тому, что приложение закрывается. Причина - PeekMessage не видит WM_QUIT :( Само приложение написано на VC с использованием MFC, так получается, что функция вызывается ДО вызова CWinApp::Run, т.е. до входа в цикл выборки сообщений, в другой ситуации вроде работает.

Ставлю брекпойнт после if (PeekMessage... - туда не попадаю. Копирую if (PeekMessage... сразу после PostQuitMessage - туда попадаю. Еще загадка - если дебаггер остановился на брекпойнт где-нибудь в MessageBoxTimer, то после этого в TimedMessageBox WM_QUIT ловится.

Дайте, плиз идею, с чем может быть связано такое поведение...


 
Suntechnic   (2003-06-15 04:38) [1]

Про чудеса с дебаггером и невидением WM_QUIT ничего сказать не могу, так как у себя воспроизвести не смог. Я вообще не смог у себя добиться некорректной работы примера, но... при подходе, реализованом в примере, возможна ситуация, когда WM_QUIT будет обрботано 2 раза. Например мы запустили TimedMessageBox с параметром 1000 мск и по истичении 999 секунды юзер вдруг решил кликнуть кнопку Ok. Ведь в этом случае наш PostQuitMessage(0); может запостить WM_QUIT в очередь сообщений потока, когда нашего диалога уже и в помине не будет. Одним словом callback ф-ция таймера может успеть сработать между вызовом MessageBox и KillTimer. Ситуация конечно маловероятная, но всё может быть.

Если не секрет, то где именно вызывается TimedMessageBox? Я пробовал в CXXXApp::InitInstance() и в конструкторе CXXXApp... всё работает на ура.




 
nikkie   (2003-06-15 14:38) [2]

>Ситуация конечно маловероятная, но всё может быть.
Наверное все зависит от того, как происходит выход из модального цикла MessageBox - если в ответ на WM_COMMAND вызывается PostQuitMessage, тогда действительно - WM_TIMER постится, поэтому согласно картинке из Рихтера может быть обработан до того, как будет получен WM_QUIT. Ситуация действительно мало вероятная, тем более и ее можно подпереть - поставить PeekMessage(WM_QUIT) перед PostQuitMessage в MessageBoxTimer.

Можно было бы послать WM_COMMAND вместо WM_QUIT, но для этого надо найти hwnd месседж-бокса, а как это сделать - непонятно.

>Если не секрет, то где именно вызывается TimedMessageBox?
Не в состоянии я это объяснить... :(
Это большой, сложный проект, на MFC написана только интерфейсная часть, причем стандартные MFC визарды не использовались - все окна создаются динамически. Надо было, конечно, писать без MFC, но когда все начиналось, я без MFC не умел. :(

То, что TimedMessageBox вызывается до вызова CWinApp::Run - просто я заметил такую особенность. Могут быть и другие особенности, единственное в чем я уверен - PostQuitMessage больше нигде не вызывается.


 
Suntechnic   (2003-06-15 17:26) [3]

>nikkie ©
Сочувствую :(
Трудно искать чёрную кошку в чёрной комнате, особенно если её там нет. Не получается у меня добиться некорретной работы, да и вся логика подсказывает, что всё вроде бы должно правильно работать.


 
nikkie   (2003-06-16 01:26) [4]

>Ситуация конечно маловероятная, но всё может быть.
Почитал еще раз Рихтера - он говорит, что WM_QUIT не помещается в очередь, вместо этого выставляется системный флаг QS_QUIT. Проверил - после нескольких PostQuitMessage подряд хватает одного PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE), чтобы этот флаг снять, поэтому описанная ситуация все же возникнуть не может.

Воспроизвел я-таки эту проблему в стандартном MFC-приложении! Последовательность действий:
1. MFC App Wizard (exe)
2. выбираю SDI или MDI (Dialog Based не подходит)
3. жму "Finish"
4. перед CMyApp::InitInstance() вставляю
void TimedMessageBox(char *text)
{
TRACE("==================================\n");
TRACE(text);
TRACE("\n");
PostQuitMessage(0);

MSG msg;
if (PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE)) {
TRACE("TimedMessageBox: QS_QUIT is set\n");
} else {
TRACE("TimedMessageBox: QS_QUIT is not set !!!!!!!!!!!!\n");
}
}

5. добавляю 2 вызова TimedMessageBox в CMyApp::InitInstance():
если это MDI
TimedMessageBox("test 1");
if (!pFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
TimedMessageBox("test 2");

если это SDI
TimedMessageBox("test 1");
pFrame->LoadFrame(IDR_MAINFRAME,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL,
NULL);
TimedMessageBox("test 2");

6. Компиляю и запускаю приложение. Окошко не показывается, приложение само завершает свою работу. В debug log-е вижу:
==================================
test 1
TimedMessageBox: QS_QUIT is set
==================================
test 2
TimedMessageBox: QS_QUIT is not set !!!!!!!!!!!!


(Вообще-то между test 1 и test 2 видны еще сообщения о загрузке нескольких системных библиотек, но думаю, что они не являются причиной такого поведения, поскольку в большом проекте я локализовал место, где меняется "QS_QUIT is set" меняется на "QS_QUIT is not set" - это вызов ShowWindow! Соответственно там в этот момент никакие dll не подгружаются.)

Система: Win2000 SP3 + IE6, компиляю на VC6 SP5.

Вопрос: Как такое может быть???


 
nikkie   (2003-06-16 02:04) [5]

исходники проекта лежат здесь:
http://schachspieler.narod.ru/PQM_Problem.html


 
Suntechnic   (2003-06-16 05:17) [6]

>nikkie © (16.06.03 02:04)
"Жить становиться лучше, жить становится веселее"
И. Сталин
:)

Читаем Рихтера:

The last function that posts a message to a thread"s queue is PostQuitMessage:

VOID PostQuitMessage(int nExitCode);

You call this function in order to terminate a thread"s message loop. Calling PostQuitMessage is similar to calling

PostThreadMessage(GetCurrentThreadId(), WM_QUIT, nExitCode, 0);


Раз господин Рихтер утверждает, что разницы нет, то модифицируем твой код:

void TimedMessageBox(char *text)
{
TRACE("==================================\n");
TRACE(text);
TRACE("\n");
//PostQuitMessage(0);
PostThreadMessage(GetCurrentThreadId(), WM_QUIT, 0, 0);
MSG msg;
if (PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE)) {
TRACE("TimedMessageBox: QS_QUIT is set\n");
} else {
TRACE("TimedMessageBox: QS_QUIT is not set !!!!!!!!!!!!\n");
}
}


Запускаем... и видим:

==================================
test 1
TimedMessageBox: QS_QUIT is set
==================================
test 2
TimedMessageBox: QS_QUIT is set


Ну что на это можно сказать? Если хочешь, то можно написать господину Рихтеру и поинтересоваться этой чертовщиной. Я на полном серьёзе говорю. Я лично как-то Страуструпу писал по поводу опечатки/ошибки в его книге, так он ответил на следующий день. Думаю Рихтер тоже не откажется.

А тут ещё в описании GetQueueStatus есть такая фраза:

The presence of a QS_ flag in the return value does not guarantee that a subsequent call to the PeekMessage or GetMessage function will return a message. GetMessage and PeekMessage perform some internal filtering that may cause the message to be processed internally.

Вот и понимай это как хочешь.


 
nikkie   (2003-06-16 15:01) [7]

Все чудесатее и чудесатее... (с) Л.Кэрролл

Налицо положительный сдвиг :), но ясности окончательной нет.

>Раз господин Рихтер утверждает, что разницы нет
Пожалуй, что он этого не утверждает, поскольку он говорит "is similar", а дальше написано:

However, PostQuitMessage doesn"t really post a message to any of the THREADINFO structure"s queues. Internally PostQuitMessage just turns on the QS_QUIT wake flag (which I"ll discuss later) and sets the nExitCode member of the THREADINFO structure. Since these actions can never fail, PostQuitMessage is prototyped as returning VOID.

Т.е. видимо PostThreadMessage(WM_QUIT) все-таки помещает сообщение в очередь и флаг QS_QUIT выставляется тогда, когда GetMessage или PeekMessage выбирает это сообщение. Кстати, это было бы логично, поскольку PostThreadMessage может быть вызвано и другим потоком и другим процессом в отличие от PostQuitMessage.

Тем не менее, замена PostQuitMessage на PostThreadMessage проблемы не решает: ведь в тестовом приложении я упростил функцию TimedMessageBox. Если использовать оригинальный код, то замена не спасает :(. Думаю этому есть объяснение: когда модальный цикл внутри MessageBox встречает WM_QUIT, то он вызывает PostQuitMessage и на самом деле ничего не меняется. (Но: если передать NULL в качестве HWND, то проблемы нет).

Дальше больше: я попробовал удалять WM_QUIT используя PeekMessage без фильтрации
BOOL bFound = FALSE;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
bFound = TRUE;
uiResult = 0;
} else {
TranslateMessage(&msg);
( &msg)
Все чудесатее и чудесатее... (с) Л.Кэрролл

Налицо положительный сдвиг :), но ясности окончательной нет.

>Раз господин Рихтер утверждает, что разницы нет
Пожалуй, что он этого не утверждает, поскольку он говорит "is similar", а дальше написано:

However, PostQuitMessage doesn"t really post a message to any of the THREADINFO structure"s queues. Internally PostQuitMessage just turns on the QS_QUIT wake flag (which I"ll discuss later) and sets the nExitCode member of the THREADINFO structure. Since these actions can never fail, PostQuitMessage is prototyped as returning VOID.

Т.е. видимо PostThreadMessage(WM_QUIT) все-таки помещает сообщение в очередь и флаг QS_QUIT выставляется тогда, когда GetMessage или PeekMessage выбирает это сообщение. Кстати, это было бы логично, поскольку PostThreadMessage может быть вызвано и другим потоком и другим процессом в отличие от PostQuitMessage.

Тем не менее, замена PostQuitMessage на PostThreadMessage проблемы не решает: ведь в тестовом приложении я упростил функцию TimedMessageBox. Если использовать оригинальный код, то замена не спасает :(. Думаю этому есть объяснение: когда модальный цикл внутри MessageBox встречает WM_QUIT, то он вызывает PostQuitMessage и на самом деле ничего не меняется. (Но: если передать NULL в качестве HWND, то проблемы нет).

Дальше больше: я попробовал удалять WM_QUIT используя PeekMessage без фильтрации
BOOL bFound = FALSE;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
bFound = TRUE;
uiResult = 0;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
if (bFound) {
TRACE("TimedMessageBox: QS_QUIT is found\n");
} else {
TRACE("TimedMessageBox: QS_QUIT is NOT found!!!\n");
}

Этот код всегда видит WM_QUIT! Видимо, проблема кроется в фильтрации PeekMessage. Конечно, не очень хорошо непосредственно использовать TranslateMessage/DispatchMessage в обход стандартной схемы обработки сообщений в MFC. Видимо именно это приводит к новой проблеме: после этого основное окно программы ни на что не реагирует. (Но: в моем реальном проекте все вроде нормально!)

Я обновил код проекта на народе, добавив несколько define-ов, чтобы можно было легко проверить все описанные варианты.

>GetQueueStatus ... Вот и понимай это как хочешь.
Попытаться это можно понять, особенно прочитав следующий абзац:
The QS_ALLPOSTMESSAGE and QS_POSTMESSAGE flags differ in when they are cleared. QS_POSTMESSAGE is cleared when you call GetMessage or PeekMessage, whether or not you are filtering messages. QS_ALLPOSTMESSAGE is cleared when you call GetMessage or PeekMessage without filtering messages (wMsgFilterMin and wMsgFilterMax are 0). This can be useful when you call PeekMessage multiple times to get messages in different ranges.

Только все равно, Рихтер пишет, что нельзя использовать GetQueueStatus для получения информации о флаге QS_QUIT.

Написать Рихтеру - идея хорошая, попробуем, что из этого выйдет :)

Кстати, в книжке у него есть другая версия TimedMsgBox, но там он использует FindWindow по заголовку, что мне совсем не нравится.


 
Suntechnic   (2003-06-16 16:09) [8]

nikkie ©
Думаю этому есть объяснение: когда модальный цикл внутри MessageBox встречает WM_QUIT, то он вызывает PostQuitMessage и на самом деле ничего не меняется.


Не думаю. Дело в том, что в ответ на WM_QUIT никто PostQuitMessage не вызывает. Логика такова, что если мы поймали WM_QUIT, то PostQuitMessage уже кто-то до нас вызвал. PostQuitMessage, обычно, вызывают в ответ на WM_DESTROY... хотя тут мне не понятен один момент: если мы всего лишь завершили message loop, то кто вызвал DestroyWindow? Но с другой стороны кто-то ж DestroyWindow вызвал раз окно прибивается... вот в ответ на этот DestroyWindow, наверное, и происходит вызов PostQuitMessage

Конечно, не очень хорошо непосредственно использовать TranslateMessage/DispatchMessage в обход стандартной схемы обработки сообщений в MFC. Видимо именно это приводит к новой проблеме: после этого основное окно программы ни на что не реагирует.

Вот это уж действительно странно. Я думаю дело здесь не в TranslateMessage/DispatchMessage (если их не вызывать эффект тот же получается). Сходу сказать почему так получается у меня, почему то, не получается :) Думаю мы просто что-то такое из очереди забираем, что не нам предназначено.
Правда, как ты понимаешь, писать просто while(PeekMessage) не совсем корректно... а если добавить WaitMessage в цикл, тогда... впрочем лучше на этом остановиться, а то ещё на работу опаздаю :)... если будет время, я ещё "поиграюсь" с твоим примером.

Написать Рихтеру - идея хорошая, попробуем, что из этого выйдет :)

Только не забудь общественность проинформировать что из этого получилось ;)


 
nikkie   (2003-06-16 17:49) [9]

>Не думаю. Дело в том, что в ответ на WM_QUIT никто PostQuitMessage не вызывает. Логика такова...
Не-е, все не так... Вот описаловка из MSDN к TimedMessageBox:

/**********************************************************************
*
* Overview
*
* The key to creating a timed message box is exiting the dialog
* box message loop internal to the message box. Since the
* message loop for a message box is part of USER, you cannot
* modify it.
*
* However, all message loops exit when they receive a
* WM_QUIT message. Furthermore, a nested message loop, if it
* receives a WM_QUIT message, must break the loop and then re-post
* the quit message so that the next outer layer can process it.
*
* Therefore, you can get the nested message loop to exit by
* calling PostQuitMessage(). The nested message loop will
* clean up and post a new quit message. When the MessageBox
* returns, you peek to see if there is a quit message. If so,
* then it means that the message loop was abnormally terminated.
* You also consume the WM_QUIT message instead of re-posting it
* so that the application continues running.
*
* Essentially, you have "tricked" the nested message loop into
* thinking that the application is terminating. When it returns,
* you "eat" the quit message, effectively canceling the fake
* quit that you generated.
*
( hwndParent, TRUE)
>Не думаю. Дело в том, что в ответ на WM_QUIT никто PostQuitMessage не вызывает. Логика такова...
Не-е, все не так... Вот описаловка из MSDN к TimedMessageBox:

/**********************************************************************
*
* Overview
*
* The key to creating a timed message box is exiting the dialog
* box message loop internal to the message box. Since the
* message loop for a message box is part of USER, you cannot
* modify it.
*
* However, all message loops exit when they receive a
* WM_QUIT message. Furthermore, a nested message loop, if it
* receives a WM_QUIT message, must break the loop and then re-post
* the quit message so that the next outer layer can process it.
*
* Therefore, you can get the nested message loop to exit by
* calling PostQuitMessage(). The nested message loop will
* clean up and post a new quit message. When the MessageBox
* returns, you peek to see if there is a quit message. If so,
* then it means that the message loop was abnormally terminated.
* You also consume the WM_QUIT message instead of re-posting it
* so that the application continues running.
*
* Essentially, you have "tricked" the nested message loop into
* thinking that the application is terminating. When it returns,
* you "eat" the quit message, effectively canceling the fake
* quit that you generated.
*
**********************************************************************/


По поводу подвисания основного окна - оно просто остается disabled после закрытия messagebox. EnableWindow(hwndParent, TRUE) спасает. Я когда искал на groups.google.com, то находил еще какие-то варианты этой функции, где запоминали/восстанавливали активное окно и делали ему Enable.

>Только не забудь общественность проинформировать что из этого получилось ;)
письмо я отправил - будет ответ, расскажу ;)


 
Suntechnic   (2003-06-16 18:55) [10]

>nikkie © (16.06.03 17:49)
Ясно. Я читал эту описаловку, но 2 дня назад... забыл уже :) Просто странно как то это выглядит: PostQuitMessage() в ответ на WM_QUIT, но такова, очевидно, реализация MessageBox. Судя по всему это сделано для того, чтобы можно было закрыть приложение по PostQuitMessage() даже в том случае, если оно ждёт ввод от пользователя через MessageBox... может это и правильно, ведь юзер может и полгода на эту кнопку не нажимать :)

оно просто остается disabled после закрытия messagebox

А с какой радости оно становится disabled на groups.google.com не говорят?

письмо я отправил - будет ответ, расскажу ;)

Let"s just wait... :)


 
Игорь Шевченко   (2003-06-18 16:49) [11]

Не знаю насчет Рихтера, но в MSDN английским по белому написано:

Do not post the WM_QUIT message using PostMessage; use
the PostQuitMessage function.


 
nikkie   (2003-06-18 18:38) [12]

Спасибо за внимание, Игорь. Но ведь мы и не пытались... Мы то посылали через PostThreadMessage, а это не запрещено. Тем более, проблема совсем не в этом.


 
Набережных С.   (2003-06-18 19:03) [13]

>Игорь Шевченко © (18.06.03 16:49)
Вообще-то и PostThreadMessage, и PostMessage прекрасно справляются с отправкой WM_QUIT. Возможно и есть какие-то нюансы, но я с ними не сталкивался.


 
nikkie   (2003-06-18 19:18) [14]

>Возможно и есть какие-то нюансы
Вот, что Рихтер пишет:

First, it is possible that posting a message could fail in very low memory situations. If an application wants to quit, it should be allowed to quit—even (or especially) in low-memory situations. The second reason is that using a flag allows the thread to finish processing all the other posted messages before the thread"s message loop terminates.


 
Набережных С.   (2003-06-18 20:00) [15]

>nikkie © (18.06.03 19:18)
Да нет, это понятно. Я имел в виду ситуации, когда PostMessage не захочет по какой-то причине поставить WM_QUIT в очередь. Если под Win32 проявляется первая причина, то "что-то в консерватории не так", тут не до WM_QUIT, нужно с алгоритмами разбираться. Ну а вторая тоже далеко не всегда имеет значение, а иногда это и требуется.

Но это, однако, все не по теме. Извиняюсь за оффтопик.



Страницы: 1 вся ветка

Форум: "WinAPI";
Текущий архив: 2003.08.21;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.54 MB
Время: 0.012 c
14-68465
Gorlum
2003-08-05 16:41
2003.08.21
fibplus4702


14-68531
Vlad Oshin
2003-08-05 10:56
2003.08.21
Глючит ZIP. iomega IDE на 250 мб


14-68526
Tahion2
2003-08-04 22:16
2003.08.21
Посоветуйте хороший рисовальщик графиков


14-68472
@@lex
2003-08-06 10:28
2003.08.21
Игральные карты


3-68249
sashag
2003-07-25 17:17
2003.08.21
Interbase DBLookupCombobox





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский