Помогите разобраться с работой API-функции MessageBox.

Тема в разделе "WASM.WIN32", создана пользователем Oleg_SK, 6 сен 2004.

  1. Oleg_SK

    Oleg_SK Guest

    Публикаций:
    0
    Привет всем!

    Прошу Вас помочь мне разобраться с тем, как работает API-функция MessageBox. Собственно, проблема вот в чем: я столкнулся с неожиданным для меня эффектом, когда использовал эту функцию в обработчике исключений (SEH) который был установлен в потоке, создавшем диалоговое окно и задачей которого была защита процедуры диалогового окна. Этот эффект проявляется вот как: если в процедуре диалога происходит исключение, а в обработчике исключения я сообщаю о возникшей ситуации пользователю (через MessageBox), то почему-то возобновляется работа потока вызвавшего исключение до того как исключение будет обработано. Таким образом, если исключение происходит при обработке сообщения от таймера (WM_TIMER), то я на экране монитора имею целую кучу сообщений об ошибке:dntknw: Кстати, аналогичную ситуацию я видел в одной фирменной программе, но только теперь понял, чем это могло быть вызвано. В общем-то, с чем-то подобным я сталкивался и раньше, когда при определенных условиях пользовался этой функцией в обработчике сообщения WM_TIMER.

    Странно, но я не нашел инфы по этой проблеме ни в одном из доступных мне доков, поэтому пришлось изобретать велосипед. В общем, я придумал для себя два варианта решения этой проблемы:

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

    2. Если функция MessageBox используется, для того чтобы не просто проинформировать пользователя о возникшей ситуации, но и предлагает выбрать, к примеру, один из двух вариантов дальнейших действий, то можно использовать глобальный флажок. Этот флажок должен устанавливаться в начале процесса обработки исключения и сбрасываться в его конце. В начале оконной (диалоговой) процедуры этот флажок проверяется и если он установлен, то происходит выход из нее без обработки сообщения (хотя тут возможны варианты).

    Вот так я сейчас делаю, но может быть существует другой более элегантный способ решения этой проблемы? И вообще, хотелось бы знать, как работает функция MessageBox, чтобы понять почему появляется эта проблема. Может быть, кто ни будь из Вас это знает? Поделитесь инфой, плиз!



    З.Ы.: Я приаттачил архив. В этом архиве находится программка, которая демонстрирует описанную здесь проблему, а также ее исходник. После запуска программы появится диалоговое окно, в котором будет находиться только одна кнопка. После того, как вы нажмете эту кнопку, в диалоговой процедуре при обработке сообщения WM_TIMER, будет происходить исключение, а после повторного нажатия генерация исключения будет прекращена. При обработке исключения будет выдаваться сообщение, после чего будет устраняться причина исключения.
     
  2. Oleg_SK

    Oleg_SK Guest

    Публикаций:
    0
  3. bogrus

    bogrus Active Member

    Публикаций:
    0
    Регистрация:
    24 окт 2003
    Сообщения:
    1.338
    Адрес:
    ukraine




    Да почти обычное окно , со своей очередью сообщений и обработчиком в user32.dll , тут оно не при чём . Если это проблема - то глобальная , вместо MessageBox ты можешь вставить хоть CreateProcess , и он будет тебе создавать НОВЫЕ и НОВЫЕ процессы , потому что WM_TIMER будет приходить пока ты не прибьёшь таймер , а MessageBox-ы будут висеть пока не нажмёшь OK .



    Думаю использования флажка самый простой способ , как вариант , можно примерно так :
    Код (Text):
    1. ;===================================================================
    2. flag dd -1
    3. ;===================================================================
    4.             cmp    flag,-1
    5.             jz     @F        ; первый раз
    6.             cmp    flag,0
    7.             jz     exit
    8. @@:         mov    flag,0
    9.             invoke MessageBox,NULL,addr Text,addr Caption,MB_OK
    10.             mov    flag,eax
    11. exit:       ret
    12. ;===================================================================
     
  4. rsrc

    rsrc New Member

    Публикаций:
    0
    Регистрация:
    4 май 2004
    Сообщения:
    24
    Адрес:
    Russia
    Oleg_SK , ты неправильно юзаешь SEH!!!

    На скорую руку я исправил тебе твою прогу, смотри аттач.

    В твоем обработчике SEH:

    1. не хватало одного параметра

    2. возвращая из него 0 (ExceptionContinueExecution) ты не восстанавлиал eip, ebp, esp



    [​IMG] 1956644938__Oleg_SK.rar
     
  5. Oleg_SK

    Oleg_SK Guest

    Публикаций:
    0
    bogrus



    Хм, почему же тогда, если я заменю вызов MessageBox на вызов Sleep, то все работает правильно (т.е. так как я ожидаю)?



    rsrc



    Вполне возможно. Я только начал изучать его использование на ASM...



    Хм, но судя по статье Джереми Гордона в обработчик передается только три параметра:

    * [esp+04h] - Указатель на структуру: EXCEPTION_RECORD;

    * [esp+08h] - Указатель на структуру: ERR;

    * [esp+0ch] - Указатель на структуру: CONTEXT record.

    и все... А что передается через четвертый параметр?





    А разве это нужно делать? В статье Джереми Гордона об этом нет ни слова. Ведь, IMHO, это система делает на автомате... Да и прога моя работает в этом плане вроде правильно...



    З.Ы.: Если я не прав, то пинайте меня...
     
  6. bogrus

    bogrus Active Member

    Публикаций:
    0
    Регистрация:
    24 окт 2003
    Сообщения:
    1.338
    Адрес:
    ukraine
    Oleg_SK




    Тю , да закоментируй вообще MessageBox , и прога будет пикать тебе каждые 2 секунды . Ты этого ожидаешь ?

    з.ы. а Sleep-ы бывают разные , попробуй Sleep,1 и Sleep,-1 :)
     
  7. Oleg_SK

    Oleg_SK Guest

    Публикаций:
    0
    bogrus



    Нет не этого. Если я заменю вызов MessageBox на следующую строку:

    invoke Sleep, 10000

    то пикать будет через каждые 10 сек...
     
  8. bogrus

    bogrus Active Member

    Публикаций:
    0
    Регистрация:
    24 окт 2003
    Сообщения:
    1.338
    Адрес:
    ukraine




    Правильно , поток будет спать 10 сек. В чём проблема то осталась ?
     
  9. Oleg_SK

    Oleg_SK Guest

    Публикаций:
    0
    bogrus



    Я ожидаю, что пока не отработает процедура обработчика исключения выполнение потока, который вызвал это исключение, не должно возобновляться.

    При Sleep, 10000 все работает правильно, т.е. выполнение потока не возобновляется (даже если поступит сообщение WM_TIMER) пока не отработает процедура обработки исключения, которая, в данном случае, работает в течении ~10 сек.

    В случае же использования функции MessageBox все летит кувырком, т.к. выполнение потока возобновляется в то время, когда процедура обработчика исключений еще не отработала:dntknw:
     
  10. volodya

    volodya wasm.ru

    Публикаций:
    0
    Регистрация:
    22 апр 2003
    Сообщения:
    1.169
    и все... А что передается через четвертый параметр?





    А-то! А твой Гордон - отнюдь не предел мечтаний :/


    Код (Text):
    1.  
    2. EXCEPTION_DISPOSITION
    3. __cdecl
    4. _except_handler(
    5.     struct _EXCEPTION_RECORD *ExceptionRecord,  //+0x4
    6.     void * EstablisherFrame,                 //+0x8
    7.     struct _CONTEXT *ContextRecord,             //+0xC
    8.     void * DispatcherContext )              //+0x10
    9. {
    10.  




    У мя в статье-то побольше расписано будет :)

    http://www.wasm.ru/article.php?article=packers2
     
  11. Oleg_SK

    Oleg_SK Guest

    Публикаций:
    0
    bogrus

    Да, что-то я запутался. Не знаю поможет тебе мой предидущий пост понять суть вопроса или нет... Ладно, давай я объясню тебе, как я тебя понял: выполнение процедуры обработки исключения будет прервано в ЛЮБОМ случае при поступлении сообщения WM_TIMER. Поэтому я привел пример, когда этого прерывания не происходит.
     
  12. Oleg_SK

    Oleg_SK Guest

    Публикаций:
    0
    volodya



    Вполне возможно, но к сожалению кроме этой статьи я больше ни чего ни где не нашел по этой теме (использование SEH на ассемблере):dntknw:



    И, кстати, Гордон - не мой;)
     
  13. rsrc

    rsrc New Member

    Публикаций:
    0
    Регистрация:
    4 май 2004
    Сообщения:
    24
    Адрес:
    Russia
    Oleg_SK, видать зря я сидел и код твой правил! Ты даже и не взглянул на него :dntknw:



    А на счет MessageBox'ов я вот, что скажу: MessageBox() - это окно и поэтому твой поток не тормозиться где-то в недрах user32.dll->MessageBox..., а вытаскивает из очереди сообщений потока мессаги, которые приходят окну(ам) потока, чтобы окно(а) могло жить и в какие-то моменты времени приходят туда и WM_TIMER'ы и пока хватает стека все у тебя хорошо... :)
     
  14. rsrc

    rsrc New Member

    Публикаций:
    0
    Регистрация:
    4 май 2004
    Сообщения:
    24
    Адрес:
    Russia
    Да, забыл сказать о том, что WM_TIMER'ы не аккумулируются или другими словами - пока енто сообщение не обработалося не приходит НОВОЕ!
     
  15. Oleg_SK

    Oleg_SK Guest

    Публикаций:
    0
    rsrc

    Oleg_SK, видать зря я сидел и код твой правил! Ты даже и не взглянул на него :dntknw:

    Извини, я сейчас снова глянул! Я так понял, что в твоем варианте программы после отработки процедуры обработчика происходит переход на код завершения программы (метка @@Exit:). Конечно, в том случае если при возникновении исключения программу нужно корректно завершить, то так и нужно делать. Но в моем же варианте в процессе обработки исключения устранялась его причина и следовательно программа могла продолжать корректно выполняться.





    Теперь понятно, откуда взялась проблема. В общем-то, я и сам мог бы догадаться. А не догадался потому, что где-то краем уха слышал, что в процессе работы MessageBox порождает новый поток, а уже в этом потоке создает окно, это меня запутало. Я не знал так это или нет, поэтому и попросил объяснить мне то, как работает эта функция.
     
  16. bogrus

    bogrus Active Member

    Публикаций:
    0
    Регистрация:
    24 окт 2003
    Сообщения:
    1.338
    Адрес:
    ukraine




    Если быть точнее , то поток "спит" (крутиться в ядре) пока очередь сообщений пуста .



    Давайте попорядку .

    1. SetTimer - говорим ядру период и адрес процедуры обработки WM_TIMER .

    2. GetMessage - говорим ядру адрес структуры MSG и уходим в ожидание (поток спит этот период) .

    3. Когда приходит время (для любого сообщения) ЯДРО пишет в структуру MSG номер сообщения и передаёт управление за GetMessage .

    4. Теперь DispatchMessage смотрит в структуру MSG и анализирует тип сообщения . Если это WM_TIMER (113h) , то передаём управление на MSG.lParam , а там уже как раз лежит (вписанный ядром) адрес нашей процедуры .



    Всё !! А теперь представьте , что у вас х-ва туча сообщений , а MSG.message это всего один дворд , ядро туда может и пишет все WM_TIMER , но DispatchMessage ведь не может создавать потоки на каждое .



    Можете поэкспериментировать на одном WM_TIMER .

    http://wasm.ru/forum/files/_934647330__timeproc.zip
     
  17. rsrc

    rsrc New Member

    Публикаций:
    0
    Регистрация:
    4 май 2004
    Сообщения:
    24
    Адрес:
    Russia
    bogrus

    Немного не так! :)



    Вот этот код дает возможность жить окну(ам):
    Код (Text):
    1.  
    2. BOOL bRet;
    3. MSG msg;
    4. while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    5. {
    6.     if (bRet == -1)
    7.     {
    8.         // handle the error and possibly exit
    9.     }
    10.     else
    11.     {
    12.         TranslateMessage(&msg);
    13.         DispatchMessage(&msg);
    14.     }
    15. }
    16.  




    Примерно тоже самое есть и у MessageBox (это же окно!) где-то :))).

    GetMessage() проверяет очередь сообщений потока на WM_TIMER в самую последнюю очередь (это сообщение низкоприоритетное!) и если находит его, то тогда тогда сбрасывает таймер и возвращает управление.



    Таймер отправляет сообщение твоему окну и ждет когда его сбросят, чтобы опять посчитать и отправить WM_TIMER, но Sleep() это (сбросить таймер) сделать не может! А вот когда ты вызываешь MessageBox(), то вот тогда (см. код выше!) его GetMessage() и сбрасывает таймер и он опять считает и дает WM_TIMER, а ты в это время зыришь свой MessageBox() и :) неожиданно для тебя выскакивает еще один….



    Oleg_SK, и вот теперь ты уже все знаешь!



    З.Ы. Дядька Рихтер толковую книжку написал.
     
  18. bogrus

    bogrus Active Member

    Публикаций:
    0
    Регистрация:
    24 окт 2003
    Сообщения:
    1.338
    Адрес:
    ukraine
    rsrc Скоро мы доберёмся до истины ! :)



    Это я привёл случай , когда процесс вообще без окон , но с обработкой WM_TIMER . Т.е. у нас крутиться свой цикл .



    Теперь , если мы добавим в процедуру Sleep , то после очередного её вызова GetMessage пойдёт уже с этой задержкой .



    Если же мы добавим MessageBox , то доставть наши сообщения (а поток у нас один) будет НЕ наш GetMessage !







    Это называеться DefDlgProc . Вот он и будет доставать теперь наши сообщения . Т.е. этот "чужой" GetMessage указывает ядру свою MSG (на стеке) , НО ЯДРО ПОМНИТ адрес нашей процедуры (WM_TIMER) и записывает его в "чужой" MSG , и 113h туда записывает . Ну а DefDlgProc тоже имеет DispatchMessage (см. пункт 4) , вот он и передаёт управление снова в нашу процедуру , а там MessageBox готовиться создать ещё одно окно (цикл они имеют все один - в DefDlgProc) .



    з.ы. Дядька Олли тоже гуд :)
     
  19. rsrc

    rsrc New Member

    Публикаций:
    0
    Регистрация:
    4 май 2004
    Сообщения:
    24
    Адрес:
    Russia
    bogrus

    1. Непонятно тогда как поток без окна может получить WM_TIMER? У него раз нет окон, то тогда нет и очереди сообщений!







    2. Не совсем DefDlgProc(), она конечно для каждого диалога вызывается, но надо же еще кое-какие мессаги обработать MessageBox'у раньше "старого" окна, MessageBox же модальное окно! У MessageBox'a должен быть еще свой цикл выборки сообщений, ведь "старый" GetMessage()-DispatchMessage() ему никак не найти!!!
     
  20. rsrc

    rsrc New Member

    Публикаций:
    0
    Регистрация:
    4 май 2004
    Сообщения:
    24
    Адрес:
    Russia
    bogrus, вот придет Four-F и нас рассудит!