RDM/2 The Russian Electronic Developer Magazine  
RDM/2 Русский электронный журнал разработчика  
ДомойОт редактораПишите намОбратная связьRU/2

Трассировка сообщений как один из способов отладки

Щелкни кобылу в нос - она махнет хвостом.
Козьма Прутков

Замечание. То, о чем пойдет речь в этой статье, скорее всего, хорошо известно. Используемые приемы давно описаны в литературе. Печально, что никто так и не собрался с силами все это связно изложить, так чтобы понятно было даже начинающим :-((. Или просто мне не попадалось подобное описание?

Чем больше пишешь программ под OS/2 (речь идет о Presentation Manager и, естественно, WPS), тем больше раздражает то, что достаточно часто упираешься в ситуацию, которую лучше всего описывает фраза, вынесенная в эпиграф. Махнуть-то она, конечно, махнет, но в каком направлении и с какой силой - известно далеко не всегда :-))

По большому счету, все работает. И даже в соответствии с документацией :-)), но... Кто не сталкивался с намертво заклинившим WPS, когда отладчик встает на точку останова, установленную внутри обработчика какого-нибудь сообщения, например, WM_PAINT? Или с трапами приложения (а то и всей системы), когда в обработчике какого-нибудь сообщения вызываешь функцию API и вдруг оказывается, что эта функция посылает на обработку это же самое сообщение, да еще и методом SEND?

Опыт приходит постепенно и начинаешь понимать, что в отладчике не стОит ставить точки останова после получения пространства отображения (presentation space), что перед использованием даже давно знакомой функции API не помешает еще раз залезть в документацию и прочитать, как и что она делает, что таймер и владельца клипборда лучше назначать в отдельные невидимые окна нулевого размера, и т.д.

Однако, все равно, остается чувство некоторой неполноценности документации и литературы (доступной, естественно) потому, что нигде не удается найти хотя бы частичное описание того, как связаны между собой различные сообщения. Например, какие сообщения и в какой последовательсти поступят на обработку после сообщения WM_CREATE, если обработчик этого сообщения вернет значение FALSE? А если TRUE? Или какие сообщения отправит на обработку функция WinDefWindowProc() в каждом конкретном случае?

Ответить на эти вопросы можно лишь с помощью трассировки сообщений. Но чем ее выполнять? Какие инструменты существуют для этого?

Отладчик для этих целей слишком груб. Кроме того, использование отладчика, хотим мы того или нет, существенно искажает общую картину поведения окна приложения, а значит и трассу сообщений. Например, пусть наше приложение запущено под отладчиком и точка останова установлена внутри какой-нибудь функции приложения, вызываемой из обработчика некоего пользовательского сообщения (как правило, это вполне безопасно и не вызывает повисания WPS и трапов). И пусть это пользовательское сообщение генерируется по нажатию функциональной клавиши F4, например. Тогда приложение будет нормально выполняться под отладчиком до тех пор, пока не будет нажата клавиша F4. Налетев на точку останова в соответствующей функции, отладчик бодро активизирует свои окна и покажет нам состояние приложения на момент останова. Но, если до останова активным было окно приложения, то после активным стало окно отладчика, а окно приложения уже успело получить и, возможно, обработать сообщение-уведомление о смене фокуса и деактивации, по крайней мере! И, если работа перехваченной функции зависит от состояния окна, то результаты ее дальнейшего выполнения будут искажены.

Использовать какие-либо внешние трассировщики сообщений тоже не очень удобно. Ну, во-первых, они как-то не попадались, а во-вторых, интересно ведь посмотреть не только какие сообщения попадают в очередь, но и коды возврата обработчиков этих сообщений, а также каким образом были получены эти коды: вернул ли их непосредственно обработчик сообщения или код был получен в результате выполнения функции WinDefWindowProc(), а эти вещи внешним трассировщиком посмотреть вряд ли удастся.

Более приемлемым представляется вариант встраивания трассировки непосредственно в приложение. Но... стряпать для этих целей отдельное окно, организовывать в него вывод сообщений, да еще и вывод в файл необходим... "Хлопотно это" (С) товарищ Сухов. Да и потом непонятно, как это дополнительное окно будет влиять на поведение основных окон приложения и, соответственно, на трассу сообщений. Гораздо проще было бы, если бы PM-приложения могли осуществлять вывод на устройство стандартного вывода stdout (если б оно у них было).

А ведь уже достаточно давно известно, как заставить VIO-приложение прикинуться PM-ным. И получается некий двухголовый монстр, который может и PM-ные окна использовать, и VIO-окно в качестве stdout. Правда, в полноэкранной сессии это работать не будет. По крайней мере, мне такая информация попала в самом начале 1998 года от Evgeny Kotsuba (спасибо ему :-)), который перекинул мне следующее сообщение от Serge Ivanov (и ему тоже спасибо! :-))) из FIDO:

  From: Serge Ivanov <Serge.Ivanov@p200.f7.n5000.z2.fidonet.org>

    Я не увеpен, что это будет pаботать в стаpых пополамах (у меня Warp 4),
    хотя скоpее всего да.
    Кто скажет, что знал об этом с детства, пусть бpосит в меня камень.
    Hайти пpименение этому, я думаю, пpоблем не составляет, начиная от
    отладки, и кончая созданием смешанных пpиложений.
  ---
  #define INCL_WIN
  #define INCL_PM
  #define INCL_DOSPROCESS
  #include <os2.h>
  #include <stdio.h>

  void main(void)
  {
     HAB hab;
     HMQ hmq;
     PPIB pib;
     PTIB tib;

     printf("In console\n");

     DosGetInfoBlocks(&tib, &pib);

  /* Try morphing into a PM application. */
     if (pib->pib_ultype == 2)          /* VIO */
        pib->pib_ultype = 3;

     hab = WinInitialize(0);
     hmq = WinCreateMsgQueue(hab, 0);
     if (!hmq)
     {
        printf("Cannot create a message queue\n");
        return;
     }

     WinMessageBox(HWND_DESKTOP, 0,
        "Now PM application", "Morphing", 100, MB_OK);
     printf("Still pm app\n");
     WinMessageBox(HWND_DESKTOP, 0,
        "Still PM application", "Morphing", 100, MB_OK);
     printf("Still pm app\n");

     WinDestroyMsgQueue(hmq);
     WinTerminate(hab);

     printf("All done\n");
  }
И тут внезапно все стало просто. В качестве подопытных кроликов были выбраны каноническая hello из IBM Developer's Toolkit for OS/2 и БГ(Болван диалоГовый). После соответствующих переделок эти PM-приложения начали бодро выдавать трассу сообщений в VIO-окно, как на устройство стандартного вывода stdout. Причем это полноценный stdout - вывод в него можно перенаправить в файл и т.д.

Вот пример трассы сообщений, создавамый программой hello, которая была запущена и тут же завершена нажатием горячей клавиши F3:

  Msg=WM_CREATE
   RC=FALSE
  Msg=WM_ADJUSTWINDOWPOS
   RC=(*)0x0
  Msg=WM_FORMATFRAME
   RC=(*)0x0 (count of SWP)
  Msg=WM_WINDOWPOSCHANGED
     Msg=WM_SIZE
      RC=(*)0x0 (reserved)
     Msg=WM_SHOW
      RC=(*)0x0 (reserved)
   RC=(*)0x0 (reserved)
  Msg=WM_QUERYFOCUSCHAIN Mp1=QFC_ACTIVE
   RC=(*)0x800001DA
  Msg=WM_QUERYFOCUSCHAIN Mp1=QFC_PARTOFCHAIN
     Msg=WM_QUERYFOCUSCHAIN Mp1=QFC_NEXTINCHAIN
      RC=(*)0x800001DA
   RC=(*)0x0
  Msg=WM_FOCUSCHANGE
     Msg=WM_QUERYFOCUSCHAIN Mp1=QFC_FRAME
      RC=(*)0x800001DA
     Msg=WM_QUERYDLGCODE
      RC=(*)0x0
     Msg=WM_QUERYFOCUSCHAIN Mp1=QFC_FRAME
      RC=(*)0x800001DA
     Msg=WM_ERASEBACKGROUND
      RC=TRUE
     Msg=WM_ACTIVATE Mp1=TRUE Mp2=0x800001DA (HWND)
      RC=(*)0x0 (reserved)
     Msg=WM_SETSELECTION
      RC=(*)0x0 (reserved)
     Msg=WM_SETFOCUS
      RC=(*)0x0 (reserved)
   RC=(*)0x0 (reserved)
  Msg=0xE
   RC=(*)0x0
  Msg=0xBD4
   RC=(*)0x0
  Msg=0xBD4
   RC=(*)0x0
  Msg=WM_PAINT
   RC=0x0 (reserved)
  Msg=WM_TRANSLATEACCEL
   RC=(*)FALSE
  Msg=WM_CHAR
   RC=(*)FALSE
  Msg=WM_TRANSLATEACCEL
     Msg=WM_INITMENU Mp1=FID_MENU
      RC=(*)0x0 (reserved)
     Msg=WM_MENUEND Mp1=FID_MENU
      RC=(*)0x0 (reserved)
   RC=(*)TRUE
  Msg=WM_COMMAND
   RC=0x0 (reserved)
  Msg=WM_CLOSE
   RC=0x0 (reserved)


  Msg=WM_QUIT (!!!)
   RC=0 (reserved)

  Msg=WM_SAVEAPPLICATION
   RC=(*)0x0 (reserved)
  Msg=WM_FOCUSCHANGE
     Msg=WM_QUERYFOCUSCHAIN Mp1=QFC_FRAME
      RC=(*)0x800001DA
     Msg=WM_SETFOCUS
      RC=(*)0x0 (reserved)
     Msg=WM_SETSELECTION
      RC=(*)0x0 (reserved)
     Msg=WM_ACTIVATE Mp1=FALSE Mp2=0x800001DA (HWND)
      RC=(*)0x0 (reserved)
   RC=(*)0x0 (reserved)
  Msg=WM_QUERYFOCUSCHAIN Mp1=QFC_ACTIVE
   RC=(*)0x800001DA
  Msg=WM_QUERYFOCUSCHAIN Mp1=QFC_PARTOFCHAIN
     Msg=WM_QUERYFOCUSCHAIN Mp1=QFC_NEXTINCHAIN
      RC=(*)0x800001DA
   RC=(*)0x0
  Msg=0xE
   RC=(*)0x0
  Msg=WM_ADJUSTWINDOWPOS
   RC=(*)0x0
  Msg=WM_WINDOWPOSCHANGED
     Msg=WM_SHOW
      RC=(*)0x0 (reserved)
   RC=(*)0x0 (reserved)
  Msg=WM_DESTROY
   RC=(*)0x0 (reserved)
Обозначения: Смещение записи сообщения вправо означает, что это сообщение поступило на обработку в тот момент, когда еще не закончена обработка предыдущего сообщения (т.е. либо обработчик предыдущего сообщения вызвал функцию API, которая иницировала передачу сообщения методом SEND, либо это сделала функция WinDefWindowProc() при выходе из обработчика предыдущего сообщения).

Кстати, сбор и анализ подобных трасс сообщений полезен не только с точки зрения отладки приложения, но и в целях изучения взаимодействия приложения с операционной средой. Это часто позволяет обнаружить и исключить код, вызывающий избыточный поток сообщений, определить правильную последовательность передачи сообщений, выявить наиболее подходящее для обработки сообщение и т.д. Достаточно сказать, что необходимость переноса процедуры сохранения параметров приложения из обработчика сообщения WM_CLOSE в обработчик сообщения WM_SAVEAPPLICATION и возможность организации выпадающих меню в программе БГ(Болван диалоГовый) были выявлены именно в результате анализа трасс сообщений приложения.

Полный текст программы hello находится в архиве hello.zip. Текст программы БГ (Болван диалоГовый) можно найти в статье " Как использовать диалог в качестве главного окна приложения". Я думаю, что Вам не составит особого труда самим дополнить его кодом, необходимым для трассировки (если потребуется :-)). Напомню еще раз, что компилироваться и линковаться такие программы должны как VIO-приложения (а не как PM)!.

Успехов, VicTor Smirnoff

Послесловие

На мой взгляд эта статья интересна не столько реализацией возможности трассировать стандартный вывод PM приложения в VIO окне, сколько самим принципом построения гибридных приложений. На самом деле подобный прием используется во многих программах: А вот стандартный вывод любой программы (не имеет значения, PM или VIO) всегда можно перенаправить в файл или использовать какую-нибудь программу-"пэйджер", например HConsole.

Теперь на счет программ трассировщиков. Ни одна из них, разумеется не сможет показать что происходит после обработки полученного сообщения тем или иным окном, но зато есть программы, которые позволяют перехватить сообщения идущие к этим окнам, посмотреть параметры этих сообщений (в PM API есть соответствующие хуки) или послать тэстовое сообщение. Вот две из них, написанные для OS/2 2.0 в 1992м-1993м годах сотрудниками IBM и до сих пор вполне работоспособные (я их гоняю в WSEB):

При использовании встроенной трассировки, может оказаться полезной библиотека Presentation Manager Monitor 1.0. Она предоставляет API для трассировки с примерами использования. Очень неплохо расшифровывает параметры сообщений.

И еще не совсем по теме, но... софт с IBM Developer Connection online:

Eugen Kuleshov

---
Интересные ссылки:

---

---
Комментариев к странице: 0 | Добавить комментарий
---
Редактор: Дмитрий Бан
Оформление: Евгений Кулешов
(C) Russian Underground/2