Отладка приложений

       

Реализация утилиты Tester


Ознакомившись с основными идеями применения Tester'a, рассмотрим некоторые важные моментам его реализации. Сначала для реализации Tester'a я использовал C++ и библиотеку активных шаблонов (Active Template Library — ATL), но затем понял, что намного лучшим выбором был бы Visual Basic. Многое из того, что предполагалось реализовать в Tester'e, не представляло особой сложности, а работу надо было выполнить побыстрее. Поэтому был сделан выбор в пользу Visual Basic, хотя, как вы увидите позже, иногда приходилось прибегать к некоторым уловкам, чтобы заставить его работать.

Первым я начал реализовывать объект класса Tinput, который является ответственным за весь ввод, посылаемый другому окну. Первоначально я думал, что обработка ввода с клавиатуры будет делом простым, — я предполагал строить оболочку вокруг VB-функции SendKeys. Этот подход прекрасно работал, когда коды отдельных клавиш посылались к Блокноту, но когда потребовалось посылать клавиши к Microsoft Outlook 2000, обнаружилось, что некоторые из них до него не доходили. Я никак не мог заставить работать SendKeys и в конце концов был вынужден реализовать собственную функцию, которую назвал PiayKeys. Раздумывая, что же следует включить в эту функцию, я заметил, что Microsoft Windows 98 и 2000 имеют изящную новую функцию — Sendinput. Эта функция также реализована в Windows NT 4, Service Pack 3 (и выше). Функция Sendinput является частью библиотеки Microsoft Active Accessibility (MSAA) и заменяет все предыдущие функции событий низкого уровня, такие как keybd_event. Функция Sendinput обрабатывает клавиатуру, мышь и события аппаратных компонентов. Она также размещает всю информацию ввода от клавиатуры или мыши в специальный входной поток (в виде непрерывного блока), гарантируя тем самым, что этот ввод не будет перемешиваться с любым посторонним пользовательским вводом. Эти функциональные возможности были особенно привлекательны для Tester'a. Быстрое тестирование показало, что функция Sendinput работала также и при посылке ввода в Outlook 2000.


После изучения правил корректной пересылки нажатий клавиш в приложения необходимо было разработать формат клавишного ввода. Поскольку оператор Visual Basic sendKeys обеспечивает удачный формат ввода, я решил просто дублировать его для функции piayKeys и оставил все, кроме кода повторения клавиши. Функция, анализирующая код клавиши, не содержит ничего особенного, — если вы заинтересуетесь этим вопросом, то загляните в каталог SourceCode\Tester\TInputHlp на сопровождающем компакт-диске. Начав работу с объектом Tinput, я все еще намеревался писать Tester на C++ и написал весь код лексического анализа в виде С++-библиотек динамической компоновки (DLL). Метод Tinput. PiayKeys — просто VB-обо-лочка для вызова этой DLL.

Объекты TWindow, Twindows и TSystem довольно просты и должны быть понятны после чтения их исходного кода. Эти три класса реализованы на Visual Basic и являются просто оболочками вокруг некоторых API-функций Windows. Создавая класс TNotify, я столкнулся с некоторыми интересными препятствиями. Задумавшись о том, как он будет определять, было ли окно с конкретным заголовком создано или разрушено, я не предполагал, что создать такой класс будет довольно трудно. Мало того, что работа была нелегкой, но оказалось также, что и уведомления о создании окна не могут быть сделаны "ошибкоустойчивыми" без героических усилий.

Первая идея состояла в том, чтобы реализовать общесистемный обработчик сообщений на основе системы СВ1. Мне показалось, что в SDK-документации говорилось о том, что СВТ-обработчик сообщений является наилучшим методом для определения моментов создания и разрушения окон. Меня подхлестнула быстрая разработка примера, но скоро я наткнулся на препятствие. Когда мой обработчик получал уведомление HCBT_CREATEWND, я не смог согласованно восстановить заголовок окна. После некоторого размышления я предположил, что СВТ-обработчик, вероятно, вызывается как часть обработки сообщения WM_CREATE, и очень немногие окна к этому моменту установили свои заголовки.


Надежное получение уведомления HCBT_CREATEWND работало только для диалоговых панелей. СВТ-обработчик всегда перехватывал разрушение окна.

 СВТ — computer-based training, компьютерное обучение. — Пер

Просмотрев все остальные типы обработчиков, я включил их в свой пример. Как я и подозревал, перехват лишь сообщения WM_CREATE не позволил мне получить заголовок. Один из друзей посоветовал перехватывать сообщения WM_SETTEXT. В конечном счете, чтобы установить заголовок в строке заголовка, почти каждое окно использует сообщение WM_SETTEXT. Конечно, если в приложении создается собственный (неклиентский) рисунок и происходит обмен данными с видеопамятью, то сообщение WM_SETTEXT не годится. Было замечено еще одно интересное обстоятельство: некоторые программы, в частности браузер Microsoft Internet Explorer, много раз последовательно отправляли сообщения WM_SETTEXT с одним и тем же текстом.

Выяснив, что нужно перехватывать сообщения WM_SETTEXT (а не WM_CREATE), я внимательнее рассмотрел различные обработчики, которые можно было использовать. В итоге выбор пал на перехват вызова оконной процедуры WH_CALLWNDPROCRET. Это позволило легко отслеживать оба сообщения — и WM_CREATE, и WM_SETTEXT. Можно также наблюдать сообщения WM_DESTROY. Сначала я ожидал некоторых неприятностей от сообщения WM_DESTROY, т. к. думал, что заголовок окна мог быть освобожден к тому моменту, когда обнаруживается это сообщение. К счастью, заголовок окна имеет силу, пока не получено сообщение WM_NCDESTROY.

Рассмотрев все "за" и "против" обработки сообщений WM_SETTEXT лишь для окон, которые еще не имели заголовка, я решил идти только вперед и обрабатывать все WM_SЕТТЕХТ-сообщения. Альтернативой было бы написание машины состояний (state machine), чтобы сохранять след созданных окон и времени установки их заголовков. Однако казалось, что такое решение чревато ошибками и его трудно реализовать. Препятствием к обработке всех WM_SЕТТЕХT-сообщений было то обстоятельство, что можно получать многократные уведомления о создании для одного и того же окна.


Например, если вы устанавливаете TNotify- обработчик для окон, заголовки которых содержат подстроку "Блокнот", то вы получите уведомление при запуске NOTEPAD.EXE, но вы также получали бы уведомление каждый раз, когда NOTEPAD.EXE открывало бы новый файл. В конце концов я почувствовал, что было бы лучше принять "менее-чем-оптимальную" реализацию, чем тратить много дней на отладку "правильного" решения. К тому же, написание собственно обработчика занимала лишь около четверти полной реализации окончательного TNotify-класса; другие три четверти кода должны были информировать пользователя о создании или разрушении окна.

Я принял решение реализовать Tester на Visual Basic прежде, чем написал класс TNotify. Ранее упоминалось, что использование Tnotify, — не полностью автоматическая операция, и что время от времени необходимо вызывать метод checkNotification. Причина заключается в том, что приложение Visual Basic не может быть многопоточным, а требовалось проверять статус окна, было ли оно создано или разрушено и продолжает ли использовать тот же поток, в котором выполнялась остальная часть TESTER.DLL.

Составив некоторое представление о механизмах уведомления, я сформулировал следующие основные требования к реализации:

  •  обработчик процедуры WH_CALLWNDPROCRET должен быть общесистемным, поэтому его нужно реализовать в отдельной DLL;
  •  очевидно, что Tester.DLL (т. е. сама утилита Tester) не может быть такой DLL, потому что перенос всей DLL VB-Tester'a и, в свою очередь, MSVBM60.DLL в адресное пространство каждого потока на компьютере пользователя. Это условие означает, что обрабатывающая DLL, вероятно, должна установить флаг или что-то еще, что может прочитать DLL Tester'a, чтобы знать, что условие выполнено;
  • Tester не может быть многопоточным, поэтому вся обработка должна выполняться в одном и том же потоке.
Первое уточнение основных требований: функция-обработчик должна быть написана на языке С. Поскольку эта функция загружается во все адресные пространства, сама DLL не может вызывать какие-либо функции из TESTER.DLL, написанной на Visual Basic.


Следовательно, мой Visual Basic- код должен периодически проверять результаты данных, сгенерированных обработчиком.

Те, кто разрабатывал 16-разрядные Windows-приложения, знают, что поиск способа организации любой фоновой обработки в однопоточной среде без прерываний всегда приводил к API-функции setTimer. С ее помощью можно было организовать фоновую обработку, сохраняя однопоточность приложения. Поэтому я установил уведомление таймера, как часть класса TNotify, чтобы определять, когда были созданы или разрушены окна, которые требовалось контролировать.

Решение в виде процедуры таймера очень похоже на ответ, но, на самом деле, оно только отчасти работает в TNotify. В зависимости от длины сценария и от того, реализует ли выбранный вами язык цикл сообщений, сообщение WM_TIMER может и не проходить, и придется вызвать метод checkNotification, который проверяет также и данные обработчика. Реализуя автоматическую проверку, я пробовал устанавливать метод TSystem. Pause, чтобы вызывать функцию DoEvents по истечении указанного (в TSystem. Pause) интервала времени. К сожалению, применение DoEvents в TSystem.pause перенесло основные проблемы производительности на сценарии, поэтому мне пришлось просто просить пользователей время от времени вызывать метод CheckNotification.

Все эти подробности реализации могут показаться довольно запутанными, но вы удивитесь, увидев, насколько компактным получился Tester. Листинг 13-3 содержит код функции-обработчика из TNOTIFYHLP.CPP. На стороне Tester'a файл TNOTIFY. BAS — это модуль, в котором постоянно хранится процедура таймера, а фактический класс реализован в файле TNOTIFY.CLS. Класс TNotify имеет пару скрытых методов и свойств, к которым модуль TNotify может обращаться, чтобы возбуждать события и определять, какие типы уведомлений хочет получать пользователь. Интересная часть кода подключения — это глобальный совместно используемый сегмент данных (.HOOKDATA), который содержит массив данных уведомлений. При просмотре кода имейте в виду, что данные уведомления глобальны, но вся остальная часть данных находится "внутри процесса".



Листинг 13-3.TNOTIFY.СРР 

/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

"Debugging Applications" (Microsoft Press)

Copyright (c) 1997-2000 John Robbins — All rights reserved.

Главный файл для TNotifyHlp.dll

- - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - * /

#include <tchar.h>

 #include <windows.h> 

#include "TNotifyHlp.h" 

/*///////////////////////////////////////////////////////////////

Определения и константы файловой области видимости

////////////////////////////////////////////////////////////////*/

 // Максимальное количество слотов уведомлений 

static const int TOTAL_NOTIFY_SLOTS = 5; 

// Имя мьютекса

static const LPCTSTR k_MUTEX_NAME = _T ( "TNotifyHlp_Mutex");

 // Максимальное время ожидания на мьютексе 

static const int k_WAITLIMIT = 5000;

// Определение собственной константы TRACE позволяет исключить

 // необходимость переноса BugslayerUtil.DLL в адресное пространство

 // каждого потока.

 #ifdef _DEBUG

#define TRACE ::OutputDebugString

 #else

#define TRACE (void)0

#endif

/*//////////////////////////////////////////////////////////////

Определение типов файловой области видимости

//////////////////////////////////////////////////////////////*/ 

// Структура для поиска индивидуального окна 

typedef struct tagJTNOTIFYITEM 

{

// PID процесса, который создал этот процесс

DWORD dwOwnerPID ;

// Тип уведомления

int iNotifyType;

// Параметр поиска

int iSearchType;

// Дескриптор для создаваемого HWND-объекта

HWND hWndCreate ;

// Булевская переменная

BOOL bDestroy ;

// Строка заголовка

TCHAR szTitle [ МАХ_РАТН ];

 } TNOTIFYITEM, * PTNOTIFYITEM;

 /*///////////////////////////////////////////////////////////////

Глобальные переменные файловой области видимости

////////////////////////////////////////////////////////////////*/ 

// Эти данные НЕ разделяются между процессами, поэтому каждый



 // процесс получает собственную копию.

// HINSTANCE- объект данного модуля. Установка глобальных обработчиков 

// системы требует DLL. 

static HINSTANCE gjnlnst = NULL; 

// Мьютекс, который защищает таблицу g_NotifyData 

static HANDLE g_hMutex = NULL;

// Обработчик перехвата. Этот дескриптор не сохраняется в разделяемой

 // секции, т. к. множественные экземпляры могут устанавливать обработчик

 // при выполнении множественных сценариев.

static HHOOK g_hHook = NULL;

// Количество элементов, добавляемых этим процессом, 

// позволяет определить, как следует обрабатывать подключение,

 static int g_iThisProcess!tems = 0; 

/*/////////////////////////////////////////////////////////////////

Прототипы файловой области видимости

////////////////////////////////////////////////////////////////*/ 

// Наш обработчик

LRESULT CALLBACK CallWndRetProcHook ( int nCode ,

WPARAM wParam, 

LPARAM IParam );

// Внутренняя проверочная функция

static LONG _stdcall CheckNotifyltem ( HANDLE hltem, BOOL bCreate);

 /*//////////////////////////////////////////////////////////////// 

Совместно используемые (разделяемле) данные для всех экземпляров обработчика

/////////////////////////////////////////////////////////////////*/

 #pragma data_seg ( ". HOOKDATA")

 // Таблица элементов уведомлений

static TNOTIFYITEM g_shared_NotifyData [ TOTAL_NOTIFY_SLOTS ] = 



{ 0,0 , 0, NULL, 0, '\0' }, 

{ 0, 0, 0, NULL, 0, '\0' }, 

{ 0, 0, 0, NULL, 0, '\0' },

 { 0, 0, 0, NULL, 0, '\0' }, 

{ 0, 0, 0, NULL, 0, '\0' } 

};

// Главный счетчик

 static int g_shared_iUsedSlots = 0;

 #pragma data_seg ()

/*////////////////////////////////////////////////////

ЗДЕСЬ НАЧИНАЕТСЯ ВНЕШНЯЯ РЕАЛИЗАЦИЯ

///////////////////////////////////////////////////*/ 

extern "C" BOOL WINAPI DllMain ( HINSTANCE hlnst ,

DWORD dwReason , 



LPVOID /*lpReserved*/)

 {

 #ifdef _DEBUG

BOOL bCHRet;

 #endif

BOOL bRet = TRUE;

 switch ( dwReason) 

{

case DLL_PROCESS_ATTACH :

// Установить экземпляр глобального модуля.

g_hlnst = hlnst;

// Нам не нужны поточные уведомления.

DisableThreadLibraryCalls ( g_hlnst);

// Создать мьютекс для этого процесса. Здесь мьютекс создан,

//но еще не присвоен.

g_hMutex = CreateMutex ( NULL, FALSE, k_MUTEX_NAME);

if ( NULL == g_hMutex)

{

TRACE ( _T ( "Unable to create the mutex!\n")); 

// Если нельзя создать мьютекс, то нельзя и 

// продолжать, поэтому отметить сбой в загрузке DLL.

 bRet = FALSE; }

break; 

case DLL_PROCESS_DETACH :

// Проверить, имеет ли этот процесс какие-то элементы в

 // массиве уведомлений. Если имеет, удалить их, чтобы

 // избежать образования потерянных (orphan) элементов.

 if (0 != g_iThisProcess!tems) 

{

DWORD dwProcID = GetCurrentProcessId ();

// Здесь не нужно захватывать мьютекс, потому что

// при наличии сообщения DLL_PROCESS_DETACH всегда будет

// вызываться только одиночный поток.

// Цикл по таблице уведомлений.

for ( int i = 0; i < TOTAL_NOTIFY_SLOTS; i++)

(

if ( g_shared_NotifyData[i].dwOwnerPID == dwProcID) 



#ifdef __DEBUG

TCHAR szBuff[ 50 ] ; 

wsprintf ( szBuff,

_T( "DLL_PROCESS_DETACH removing : #%d\n"),

i);

TRACE ( szBuff);

 #endif

// Избавляемся от сообщения. RemoveNotifyTitle ( (HANDLE)i); 





}

// Закрыть дескриптор мьютекса.

 #ifdef _DEBUG

bCHRet =

 #endif

CloseHandle ( g_hMutex);

 #ifdef _DEBUG

 if ( FALSE == bCHRet) 

{

TRACE ( "!!!!!!!!!!!!!!!!!!!!!!!!\n"); 

TRACE { "CloseHandle(gJiMutex) "

"failed!!!!!!!!!!!!!!!!!!\n"); 

TRACE ( "!!!!!!!!!!!!!!!!!!!!!!!!\n");

 }

#endif

break; 

default :

break; 

}

return ( bRet); 

}

HANDLE TNOTIFYHLP_DLLINTERFACE _stdcall



 AddNotifyTitle ( int iNotifyType, 

int iSearchType, 

LPCTSTR szString )

 {

// Убедитесь, что диапазон типов уведомлений корректен, 

if ( ( iNotifyType < ANTN_DESTROYWINDOW ) || 

( iNotifyType > ANTN_CREATEANDDESTROY ) ) 

{

TRACE ( "AddNotify Title : iNotifyType is out of range!\n"); 

return ( INVALID_HANDLE_VALUE); 

}

// Убедитесь, что диапазон типов поиска корректен,

 if ( ( iSearchType < ANTS_EXACTMATCH ) || 

( iSearchType > ANTS_ANYLOCMATCH) )

{

TRACE ( "AddNotify Title : iSearchType is out of range!\n");

return ( INVALID_HANDLE_VALUE); 

}

// Убедитесь, что строка правильная, 

if ( TRUE == IsBadStringPtr ( szString, MAX_PATH)) 

{

TRACE ( "AddNotify Title : szString is invalid!\n");

return ( INVALID_HANDLE_VALUE); 

}

// Ждать получения мьютекса.

DWORD dwRet = WaitForSingleObject ( g_hMutex, k_WAITLIMIT);

 if ( WAIT_TIMEOUT == dwRet) 

{

TRACE ( _T( "AddNotifyTitle : Wait on mutex timed out!!\n"));

return ( INVALID_HANDLE_VALUE); 

}

// Если все слоты использованы, то — аварийный останов.

 if ( TOTAL_NOTIFY_SLOTS == g_shared_iUsedSlots) 

{

ReleaseMutex ( g_hMutex);

return ( INVALID_HANDLE_VALUE); 

}

// Найти первый свободный слот,

 for ( int i = 0; i < TOTAL_NOTIFY_SLOTS; i++)

{

if ( _T ( '\0') == g_shared_NotifyData[ i ].szTitle[ 0 ])

{

 break;

}

 }

// Добавить эти данные.

g_shared_NotifyData[ i ].dwOwnerPID = GetCurrentProcessId ();

 g_shared_NotifyData[ i ].iNotifyType = iNotifyType; 

g__shared_NotifyData[ i ].iSearchType = iSearchType;

 Istrcpy ( g_shared__Notif yData [ i J.szTitle, szString); 

// Увеличить главный счетчик

. g_shared_iUsedSlots++;

 // Увеличить счетчик этого процесса. 

g_iThis Process Items++;

TRACE ( "AddNotifyTitle - Added a new item!\n"); 

ReleaseMutex ( g_hMutex);



// Если это первый запрос уведомления, активизировать подключение.

 if ( NULL = g_hHook)

 {

g_hHook = SetWindowsHookEx ( WH_CALLWNDPROCRET ,

CallWndRetProcHook, g_hlnst , 0 );

 #ifdef _DEBUG

if ( NULL == g_hHook)

 {

char szBuff[ 50 ];

 wsprintf ( szBuff,

_T ( "SetWindowsHookEx failed!!!! (Ox%08X)\n"), GetLastError ()); 

TRACE ( szBuff); 

}

#endif 

}

return ( (HANDLE)!); 



void TNOTIFYHLP_DLLINTERFACE _stdcall

RemoveNotifyTitle ( HANDLE hltem) 

{

// Проверить значение.

int i = (int)hltem;

if ( ( i < 0) || ( i > TOTAL_NOTIFY_SLOTS))

{

TRACE ( _T ( "RemoveNotifyTitle : Invalid handle!\n"));

  return; 

}

// Получить мьютекс.

DWORD dwRet = WaitForSingleObject ( g_hMutex, k_WAITLIMIT);

 if ( WAIT_TIMEOUT == dwRet) 

{

TRACE ( _T ( "RemoveNotifyTitle : Wait on mutex timed out!\n"));

  return; 

}

if ( 0 = g_shared_iUsedSlots) 

{

TRACE ( _T ( "RemoveNotifyTitle : Attempting to remove when "

"no notification handles are set!\n")); 

ReleaseMutex ( g_hMutex);

  return; 

}

// Перед удалением чего-то, удостоверьтесь, что этот индекс указывает 

// на вход NotifyData, который содержит правильное значение. Без

 // проверки можно вызывать эту функцию с тем же значением

 // несколько раз, что запортит индексы используемых слотов.

if ( 0 == g_shared_NotifyData[ i ].dwOwnerPID) 

{

TRACE ( "RemoveNotifyTitle : Attempting to double remove!\n");

ReleaseMutex ( g_hMutex);

return; 

}

// Удалить этот элемент из массива. 

g_shared_NotifyData[ i ].dwOwnerPID =0; 

g_shared_NotifyData[ i ].iNotifyType = 0;

 g_share.d_NotifyData [ i ] .hWndCreate = NULL;

 g_shared_NotifyData[ i ].bDestroy = FALSE; 

g_shared_NotifyData[ i ].iSearchType = 0;

 g_shared_NotifyData[ i ].szTitle[ 0 ] = _T ( '\0');

 // Декремент главного счетчика элементов.



 g_shared_iUsedSlots- -;

// Декремент счетчика элементов этого процесса.

 g_iThisProcessItems- -;

TRACE ( _Т ( "RemoveNotifyTitle - Removed an item!\n")); 

ReleaseMutex ( g_hMutex);

// Если это последний элемент данного процесса, завершить 

// его обработку.

if ( ( 0 == g_iThisProcess!tems) && ( NULL != g_hHook)) 

{

if ( FALSE = UnhookWindowsHookEx ( g_hHook))

{

TRACE ( _T ( "UnhookWindowsHookEx failed!\n"));

}

g_hHook = NULL; 



} HWND TNOTIFYHLP_DLLINTERFACE _stdcall

CheckNotifyCreateTitle ( HANDLE hltem)

{

return ( (HWND)CheckNotifyltem ( hltem, TRUE));

}

BOOL TNOTIFYHLP_DLLINTERFACE _stdcall

CheckNotifyDestroyTitle ( HANDLE hltem) 

{

return ( (BOOL)CheckNotifyltem ( hltem, FALSE)); 



/*///////////////////////////////////////////////////////////////

ЗДЕСЬ НАЧИНАЕТСЯ ВНУТРЕННЯЯ РЕАЛИЗАЦИЯ

////////////////////////////////////////////////////////////////*/ 

static LONG _stdcall CheckNotifyltem { HANDLE hltem, BOOL bCreate)

{

// Проверить значение.

int i = (int)hltem;

if ( ( i < 0) || ( i > TOTAL_NOTIFY_SLOTS))

{

TRACE ( _T ( "CheckNotifyltem : Invalid handle!\n"));

return ( NULL); 

}

LONG IRet = 0; 

// Получить мьютекс.

DWORD dwRet = WaitForSingleObject ( g_hMutex, k_WAITLIMIT);

 if ( WAIT_TIMEOUT == dwRet) 

{

TRACE ( _T ( "CheckNotifyltem : Wait on mutex timed out!\n"));

return ( NULL);

 }

// Если все слоты пусты, освобождаем мьютекс.

 if ( 0 = g_shared_iUsedSlots) 

{

ReleaseMutex ( g_hMutex);

return ( NULL); 

}

// Проверить затребованный элемент, 

if ( TRUE == bCreate)

 {

// Если HWND-значение не NULL, возвратить это значение

//и обнулить его в таблице.

if ( NULL != g_shared_NotifyData[ i ].hWndCreate)

{

IRet = (LONG)g_shared_NotifyData[ i ].hWndCreate; 

g_shared_NotifyData[ i ].hWndCreate = NULL;

}

 }

else 

{

if ( FALSE != g_shared_NotifyData[ i ].bDestroy)



{

IRet = TRUE;

g_shared_NotifyData[ i ].bDestroy = FALSE;



}

ReleaseMutex ( g_hMutex); 

return ( IRet); 

}

static void _stdcall CheckTableMatch ( int iNotifyType,

HWND hWnd , 

LPCTSTR szTitle ) {

// Захватить мьютекс.

DWORD dwRet = WaitForSingleObject ( g__hMutex, k_WAITLIMIT);

if ( WAIT_TIMEOUT == dwRet)

{

TRACE ( _T ( "CheckTableMatch : Wait on mutex timed out!\n"));

  return; 

}

// Таблица не должна быть пустой, но все надо проверять. 

if ( 0 == g_shared_iUsedSlots) 

{

ReleaseMutex ( g_hMutex);

TRACE { _T ( "CheckTableMatch called on an empty table!\n")); 

return;

 }

// Поиск в таблице.

for ( int i = 0; i < TOTAL_NOTIFY_SLOTS; i++) 

{

// Содержит ли что-нибудь эта запись и согласован ли тип 

// уведомления?

if- ( ( _Т ( '\0') != g_shared_NotifyData[ i ].szTitle[ 0 ]) && 

( g__shared_NotifyData[ i ].iNotifyType & iNotifyType ) ) 

{

BOOL bMatch = FALSE;

// Выполнить согласование.

switch ( g_shared_NotifyData[ i ].iSearchType)

{

case ANTS_EXACTMATCH :

 // Это просто, 

if ( 0 = Istrcmp ( g_shared_NotifyData[i].szTitle,

szTitle )) 

{

bMatch = TRUE; 

}

break;

case ANTS_BEGINMATCH : 

if ( 0 ==

 _tcsnccmp ( g_shared_NotifyData[i].szTitle, 

 szTitle , strlen(g_shared_NotifyData[i].szTitle))) 

{

bMatch = TRUE;

 }

break; case ANTS_ANYLOCMATCH :

if ( NULL != _tcsstr ( szTitle

g_shared_NotifyData[i].szTitle)) 

{

bMatch = TRUE; 

}

break; 

default :

TRACE ( _T ( "CheckTableMatch invalid "\ "search type!!!\n"));

 ReleaseMutex ( g_hMutex);

  return; 

break; 

}

// Согласование выполнено?

 if ( TRUE = bMatch) 

{

// Если это уведомление об уничтожении, проставить "1"

// в таблице.

if ( ANTN_DESTROYWINDOW == iNotifyType)

{

g_shared_NotifyData[ i ].bDestroy = TRUE;



 }

else 

{

// В противном случае, проставить в таблице HWND.

 g_shared_NotifyData[ i ].hWndCreate - hWnd;

 } 





}

ReleaseMutex ( g_hMutex);

 }

LRESULT CALLBACK CallWndRetProcHook ( int nCode ,

WPARAM wParam,

 LPARAM IParam ) 

{

// Буфер для хранения заголовка окна

TCHAR szBuff[ МАХ_РАТН ];

// Всегда передавать сообщение следующему обработчику, прежде чем

// приниматься за какую-нибудь обработку.

LRESULT IRet = CallNextHookEx ( gJnHook, nCode, wParam, IParam);

// The docs say never to mess around with a negative code, so I don't.

// Документация просит никогда не оставлять без внимания

// отрицательные значения, вот я и не оставляю.

if ( nCode < 0) 

{

return ( IRet);

 }

// Получить структуру сообщения. Почему там три (или больше)

 // различных структуры сообщений? Где ошибка в 

// последовательном использовании запаса ole-сообщений для всех 

// обработчиков сообщений/процессов? 

PCWPRETSTRUCT pMsg = (PCWPRETSTRUCT)IParam; 

// Нет заголовка, нет работы

LONG IStyle = GetWindowLong ( pMsg->hwnd, GWL_STYLE); 

if ( WS_CAPTION != ( IStyle & WS_CAPTION)) {

return ( IRet); 

}

// Сообщения WM_DESTROY прекрасно подходят и для диалоговых,

 // и для нормальных окон. Только получите заголовок и проверьте

 // согласование.

if ( WM_DESTROY == pMsg->message) 

{

if (0 != GetWindowText ( pMsg->hwnd, szBuff, MAX_PATH))

{

CheckTableMatch ( ANTN_DESTROYWINDOW, pMsg->hwnd, szBuff);

}

return ( IRet); 

}

// Создание окна сложнее, чем его уничтожение.

 // Получить класс окна. Если это на самом деле диалоговое окно, 

//то нам нужно только сообщение WM_INITDIALOG.

 if ( 0 == GetClassName ( pMsg->hwnd, szBuff, MAX_PATH)) 

{

#iifdef _DEBUG

TCHAR szBuff[ 50 ];

wsprintf ( szBuff ,

 _T ( "GetClassName failed for HWND : 0x%08X\n"), 

pMsg->hwnd ) ;



TRACE ( szBuff); 

#endif

// He так уж много точек, куда можно перейти

return ( IRet); 

}

if ( 0 == Istrcmpi ( szBuff, _T ( "#32770"))) 

{

// Единственное сообщение, которое нужно проверить,

// - ЭТО WM_INITDIALOG.

if ( WM_INITDIALOG == pMsg->message)

{

// Получить заголовок диалогового окна.

if (0 != GetWindowText ( pMsg->hwnd, szBuff, MAX_PATH))

{

CheckTableMatch ( ANTN_CREATEWINDOW,

 pMsg->hwnd ,

 szBuff );

 } 

}

return ( IRet); 

}

//

// Далее речь пойдет о настоящих диалоговых окнах.

 // Решаем, что делать с текущими окнами, 

if ( WM_CREATE == pMsg->message) 

{

// Очень немногие окна устанавливают заголовок в сообщении

// WM_CREATE. Однако некоторые это делают и не используют

// сообщения WM_SETTEXT, поэтому требуется проверка.

if (0 != GetWindowText ( pMsg->hwnd, szBuff, MAX_PATH))

{

CheckTableMatch ( ANTN_CREATEWINDOW, 

pMsg->hwnd , 

szBuff ); 

}

 }

else if ( WM_SETTEXT = pMsg->message)

 {

// Я всегда устанавливаю по умолчанию WM_SETTEXT, потому что эта

// установка используется для заголовков. К сожалению, некоторые

// приложения, такие как Internet Explorer, вызывают WM_SETTEXT

// многократно'с одним и тем же заголовком. Чтобы не усложнять

// этот обработчик, я просто сообщаю WM_SETTEXT вместо того,

// чтобы поддерживать различные малопонятные и тяжело

// отлаживаемыеструктуры данных, которые сохраняют сведения

// об окнах и предварительно вызывают сообщение WM_SETTEXT.

if ( NULL != pMsg->lParam)

{

CheckTableMatch ( ANTN_CREATEWINDOW , 

pMsg->hwnd , 

(LPCTSTR)pMsg->lParam ); 



}

return ( IRet); 

}

Хотя реализация TNotify была довольно трудноразрешимой задачей, я все-таки остался доволен, что при этом почти не испытал неприятностей. Если собираетесь расширить код обработчика, то уже знаете, что отладка общесистемных обработчиков — непростая задача.


Для ее выполнения вполне годится отладчик Microsoft Visual C++, но я никогда не пытался этого делать. Я использую только отладчик SoftlCE. Существует еще один способ отладки общесистемных подключений. Чтобы воспользоваться им, следует обратиться к так называемому printf-стилю отладки. Применение утилиты DBGVIEW позволяет наблюдать все вызовы функции OutputDebugString и видеть, таким образом, состояние обработчика.

Я столкнулся с одной раздражающей проблемой при разработке Tester'a, которая появлялась только в Windows 98. Весь тест-код прекрасно работал в Windows NT 4 и Windows 2000, но в Windows 98 невозможно было заполнить коллекцию TWindows. Я проверял правильность дескриптора HWND окна, передаваемого в метод Add, с помощью функции iswindow. Беглое чтение документации подсказало, что iswindow возвращает значение типа BOOL. Я ошибочно предположил, что TRUE соответствовало положительным значениям, а FALSE — отрицательным. А в условных конструкциях, я использовал выражение в форме 1 = iswindow (hWndT), которая, очевидно, не работала. Как можно предположить, различные операционные системы не возвращают одни и те же значения. Этот "маленький", но досадный промах весьма поучителен.



Содержание раздела