
Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdf

594 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
return ( lRet ) ;
}
//Получение структуры сообщения. Интересно, зачем нужны три
//(или больше) различных структуры сообщений? Почему нельзя
//было использовать структуру MSG для всех сообщений/ловушек? PCWPRETSTRUCT pMsg = (PCWPRETSTRUCT)lParam ;
//Нет заголовка — нет работы.
LONG lStyle = GetWindowLong ( pMsg >hwnd , GWL_STYLE ) ; if ( WS_CAPTION != ( lStyle & WS_CAPTION ) )
{
return ( lRet ) ;
}
//Сообщения WM_DESTROY используются и диалоговыми, и обычными
//окнами. Нужно просто получить заголовок окна и проверить
//наличие соответствующего элемента в таблице уведомлений.
if ( WM_DESTROY == pMsg >message )
{
if ( 0 != GetWindowText ( pMsg >hwnd , szBuff , MAX_PATH ) )
{
CheckTableMatch ( ANTN_DESTROYWINDOW , pMsg >hwnd , szBuff ) ;
}
return ( lRet ) ;
}
//С созданием окна все не так просто, как с его уничтожением.
//Получение класса окна. Если это подлинное диалоговое
//окно, мне нужно только сообщение WM_INITDIALOG.
if ( 0 == |
GetClassName ( pMsg >hwnd , szBuff , MAX_PATH ) ) |
|
{ |
|
|
#ifdef _DEBUG |
|
|
TCHAR |
szBuff[ 50 ] ; |
|
wsprintf ( szBuff |
, |
|
|
_T ( "GetClassName failed for HWND : 0x%08X\n" ) , |
|
|
pMsg >hwnd |
) ; |
TRACE |
( szBuff ) ; |
|
#endif |
|
|
// Продолжение не имеет смысла. return ( lRet ) ;
}
if ( 0 == lstrcmpi ( szBuff , _T ( "#32770" ) ) )
{
// Мне нужно проверять только сообщение WM_INITDIALOG. if ( WM_INITDIALOG == pMsg >message )
{
// Получение заголовка диалогового окна.
if ( 0 != GetWindowText ( pMsg >hwnd , szBuff , MAX_PATH ) )

ГЛАВА 16 Автоматизированное тестирование |
595 |
|
|
|
|
|
|
|
|
|
|
{ |
|
|
CheckTableMatch ( ANTN_CREATEWINDOW |
, |
|
pMsg >hwnd |
, |
|
szBuff |
) ; |
|
} |
|
|
} |
|
|
return ( lRet ) ; |
|
|
} |
|
|
// Я разобрался с диалоговыми окнами. Теперь |
|
|
// мне нужно позаботиться о других окнах. |
|
|
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 ( lRet ) ;
}
Некоторые аспекты реализации TNotify казались довольно сложными, поэто! му я был приятно удивлен тем, как мало проблем я испытал на самом деле. Если вы хотите усовершенствовать код функции!ловушки, знайте, что отлаживать сис! темные ловушки очень непросто. Для этого лучше всего использовать удаленную отладку (см. главу 5). Еще один способ отладки системных ловушек заключается в отладке в стиле printf. Программа DebugView, которую можно загрузить с сайта


ГЛАВА 16 Автоматизированное тестирование |
597 |
|
|
CELJOURNAL. Когда пользователь нажимает Ctrl+Alt+Delete, ОС завершает все актив! ные ловушки записи журнала. Это очень грамотное решение, так как возможность записи нажатий клавиш при вводе пароля открывала бы огромную брешь в сис! теме безопасности. Чтобы скрыть детали реализации обработки WM_CANCELJOURNAL, я написал фильтр, отслеживающий это сообщение. Все подробности работы фун! кции!ловушки вы можете увидеть в файле HOOKCODE.CPP, находящемся в ката! логе Tester\TestRec.
Обработка ввода с клавиатуры
Запись событий клавиатуры сводится главным образом к правильной обработке нажатий клавиш Shift, Ctrl и Alt. Прежде чем я опишу некоторые аспекты борьбы с нажатиями отдельных клавиш, изучите рисунки 16!2 — 16!4, на которых пред! ставлен упрощенный граф всех состояний клавиатуры, обрабатываемых кодом записи сценария.
|
|
НормальноеNormal |
|
|
|
|
состояниеState |
|
|
Клавиша |
|
Введенная клавиша |
|
Клавиша |
|
|
|
||
отпущена |
|
|
|
отпущена |
Тип |
VK_MENU |
Тип ввода |
VK_CANCEL |
Тип |
сообщения |
сообщения |
|||
Клавиша нажата |
|
|
|
Клавиша нажата |
Состояние |
|
Любая другая клавиша |
|
Состояние |
IsTabKey |
|
|
|
CheckBreak |
|
|
|
|
Запись |
Обработка |
|
Тип ввода |
VK_SHIFT |
Shift + клавиша |
|
|
|
|
нажата/отпущена |
Рис. 16 3. Конечный автомат нормальной обработки событий клавиатуры
Первая проблема записи событий клавиатуры заключается в получении их из функции!ловушки в понятной человеку форме. Если вы никогда не испытывали радость работы с виртуальными и скан!кодами, вы не знаете, что такое настоя! щие трудности! Кроме того, я обнаружил, что некоторые данные, получаемые ловушкой записи журнала довольно сильно отличались от того, что я ожидал.
Последний раз я работал с клавиатурой на этом уровне во времена MS!DOS (похоже, я выдал свой возраст!). Поэтому я внес в проблему некоторые дополни! тельные недоразумения. Например, когда я впервые ввел восклицательный знак, я ожидал увидеть, что именно этот символ и поступит в функцию!ловушку. Одна! ко вместо этого я получил символ Shift, за которым следовала единица. Именно так восклицательный знак вводится с клавиатур US English. Однако я хотел, что! бы все воспроизводимые мной последовательности нажатых клавиш были пре!

598 |
ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода |
||
дельно понятны. Последовательность SendKeys «+1» с технической точки зрения |
|||
верна, но при этом нужно проделать некоторую умственную гимнастику, чтобы |
|||
понять, что на самом деле это символ «!». |
|
||
|
Состояние |
|
|
|
Alt Tab |
|
|
|
Любое входное сообщение |
VK_TAB or VK_SHIFT |
|
Любая |
|
|
|
другая |
Тип ввода |
WM_SYSKEYDOWN |
Клавиша |
клавиша |
|
|
|
|
WM_SYSKEYUP |
|
WM_SYSKEYDOWN |
|
|
|
|
|
Клавиша |
|
Игнорируем |
|
|
Alt+Tab |
|
|
|
|
VK_MENU
Игнорируем |
Да |
|
Alt+Tab? |
||
|
||
Нет |
|
|
Определение |
Начальное |
|
состояние |
||
состояния фокуса |
||
|
Прервать |
Да |
В фокусе |
Нет |
|
запись |
при TestRec? |
|||
|
|
Рис. 16 4. Конечный автомат обработки комбинации Alt+Tab
Чтобы программа TESTREC.EXE была максимально полезна, я должен был реа! лизовать некоторый специальный механизм обработки, который позволил бы сделать выводимые строки понятными. Проще говоря, я должен был проверить состояние клавиатуры, узнать, нажата ли клавиша Shift, и, если да, преобразовать символ в понятную форму. К счастью, для получения действительного символа у нас есть API!функции GetKeyboardState и ToUnicode. Чтобы понять суть обработки нажатий клавиш, изучите функцию CRecordingEngine::NormalKeyState из листинга 16!4.
Листинг 16-4. CRecordingEngine::NormalKeyState
void CRecordingEngine :: NormalKeyState ( PEVENTMSG pMsg )
{
// Состояние, в которое будет выполнен переход




602 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
// Это национальный символ.
ASSERT ( !"I gotta handle this!" ) ;
}
//Если символ не был преобразован,
//cChar не используется!
if ( 0 == iRet )
{
cChar = (TCHAR)vkCode ;
}
else
{
//Прежде чем записать символ, мне нужно узнать,
//нажата ли клавиша CTRL. Если да, то функции
//ToAscii/ToUnicode возвращают управляющий
//код ASCII. Так как мне нужен символ, я должен
//выполнить некоторую дополнительную работу. SHORT sCtrlDown =
GetAsyncKeyState ( VK_CONTROL ) ; if ( 0xFFFF8000 == ( 0xFFFF8000 & sCtrlDown ))
{
//Клавиша CTRL нажата, поэтому мне нужно
//узнать состояние клавиш CAPSLOCK и SHIFT. BOOL bCapsLock =
( 0xFFFF8000 == ( 0xFFFF8000 & GetAsyncKeyState ( VK_CAPITAL)));
if ( TRUE == bCapsLock )
{
//Если нажаты клавиши CAPSLOCK и SHIFT,
//используем символ нижнего регистра. if ( TRUE == m_bShiftDown )
{
//Запрещение предупреждения 'variable' : conversion from 'type' to 'type'
//of greater size (преобразование к типу, имеющему больший размер). #pragma warning ( disable : 4312 )
cChar = (TCHAR)
CharLower ( (LPTSTR)vkCode );
#pragma warning ( default : 4312 )
}
else
{
// Символ верхнего регистра. cChar = (TCHAR)vkCode ;
}
}
else
{
//Клавиша CAPSLOCK не нажата,
//поэтому проверяется только
//клавиша SHIFT.