На этом шаге мы рассмотрим создание и использование ловушек.
Мы рассмотрим весьма действенное средство, чаще всего используемое для отладки программ. Средство это называют фильтрами или ловушками. Смысл его заключается в том, что вы при желании можете отслеживать сообщения как в рамках одного приложения, так и в рамках целой системы. В этой связи фильтры делят на глобальные (в рамках всей системы) и локальные (в рамках данного процесса). Работая с фильтрами, надо иметь в виду, что они могут существенным образом затормозить работу всей системы. Особенно это касается глобальных фильтров. С точки зрения программирования, мы просто определяем функцию, которая вызывается системой при возникновении некоторого события. Можно также говорить о сообщении, приходящем на функцию фильтра.
Рассмотрим некоторые средства для работы с фильтрами. Ниже перечислены основные типы фильтров или сообщения.
Фильтр устанавливается при помощи функции SetWindowsHookEx. Рассмотрим параметры этой функции.
Функция SetWindowsHookEx возвращает дескриптор фильтра.
Функция фильтра получает три параметра. Первый параметр определяет произошедшее событие в зависимости от типа фильтра. Два последующих параметра расшифровывают это событие. Поскольку для каждого типа фильтра может быть несколько событий, мы не будем их перечислять. Их можно найти в справочном руководстве.
По окончании работы фильтр обязательно должен быть закрыт с помощью функции UnhookWindowsHookEx, единственным параметром которой является дескриптор фильтра.
Фильтр, вообще говоря, есть лишь некоторое звено в вызываемой системой цепочке, поэтому следует из своей процедуры фильтра вызвать функцию CallNextHookEx, которая передаст нужную информацию по цепочке. Параметры этой функции:
В следующей программе приводится пример простого фильтра, который отлавливает все произошедшие в системе нажатия клавиши Пробел. Обратите внимание, что поскольку устанавливаемый нами фильтр является глобальным, мы помещаем процедуру фильтра в динамическую библиотеку.
Файл pr82_1.rc:
//Определение констант. #define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L //Определение диалогового окна. DIAL1 DIALOG 0, 0, 240, 120 STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX CAPTION "Пример программы с фильтром" FONT 8, "Arial" { }
Файл pr82_1.asm - основной модуль:
.386P ;Плоская модель. .MODEL FLAT, STDCALL ;Константы. STD_OUTPUT_HANDLE equ -11 GENERIC_READ equ 80000000h OPEN_EXISTING equ 3 ;Прототипы внешних процедур. IFDEF MASM EXTERN GlobalFree@4:NEAR EXTERN GlobalAlloc@8:NEAR EXTERN GetFileSize@8:NEAR EXTERN CloseHandle@4:NEAR EXTERN CreateFileA@28:NEAR EXTERN ReadFile@20:NEAR EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR ELSE LOCALS EXTERN GlobalFree:NEAR EXTERN GlobalAlloc:NEAR EXTERN GetFileSize:NEAR EXTERN CloseHandle:NEAR EXTERN CreateFileA:NEAR EXTERN ReadFile:NEAR EXTERN GetStdHandle:NEAR EXTERN WriteConsoleA:NEAR EXTERN ExitProcess:NEAR EXTERN GetCommandLineA:NEAR GlobalFree@4 = GlobalFree GlobalAlloc@8 = GlobalAlloc GetFileSize@8 = GetFileSize CloseHandle@4 = CloseHandle CreateFileA@28 = CreateFileA ReadFile@20 = ReadFile GetStdHandle@4 = GetStdHandle WriteConsoleA@20 = WriteConsoleA ExitProcess@4 = ExitProcess GetCommandLineA@0 = GetCoramandLineA ENDIF ;Директивы компоновщику для подключения библиотек. IFDEF MASM ;Для компоновщика LINK.EXE. includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib ELSE ;Для компоновщика TLINK32.EXE. includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------ ;Сегмент данных. _DATA SEGMENT DWORD PUBLIC USE32 'DATA' LENS DWORD ? ;Количество выведенных символов. HANDL DWORD ? ;Дескриптор консоли. HF DWORD ? ;Дескриптор файла. SIZEH DWORD ? ;Старшая часть длины файла. SIZEL DWORD ? ;Младшая часть длины файла. GH DWORD ? ;Указатель на блок памяти. NUMB DWORD ? BUF DB 100 DUP (0) _DATA ENDS ;Сегмент кода. _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ;Получить дескриптор вывода. PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ;Получить количество параметров. CALL NUMPAR CMP EAX,2 JB _EXIT ;------------------------------------------------ ;Получить параметр номером EDI. MOV EDI,2 LEA EBX,BUF CALL GETPAR ;Теперь работаем с файлом. ;Открыть только для чтения. PUSH 0 PUSH 0 PUSH OPEN_EXISTING PUSH 0 PUSH 0 PUSH GENERIC_READ PUSH OFFSET BUF CALL CreateFileA@28 CMP EAX,-1 JE _EXIT ;Запомнить дескриптор файла. MOV HF,EAX ;Определить размер файла. PUSH OFFSET SIZEH PUSH EAX CALL GetFileSize@8 ;Запомнить размер, предполагаем, что размер не превосходит 4 Гбайт. MOV SIZEL,EAX ;Запросить память для считывания туда файла. PUSH EAX PUSH 0 CALL GlobalAlloc@8 CMP EAX,0 JE _CLOSE ;Запомнить адрес выделенного блока. MOV GH,EAX ;Читать файл в выделенную память. PUSH 0 PUSH OFFSET NUMB PUSH SIZEL PUSH GH PUSH HF CALL ReadFile@20 CMP EAX,0 JE _FREE ;Вывести прочитанное. PUSH 0 PUSH OFFSET LENS PUSH SIZEL PUSH GH PUSH HANDL CALL WriteConsoleA@20 _FREE: ;Освободить память. PUSH GH CALL GlobalFree@4 ;Закрыть файлы. _CLOSE: PUSH HF CALL CloseHandle@4 _EXIT: ;Конец работы программы. PUSH 0 CALL ExitProcess@4 ;**************************** ;Область процедур. ;**************************** ;Определить количество параметров (->ЕАХ) NUMPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ;Указатель на строку. XOR ECX,ECX ;Счетчик. MOV EDX,1 ;Признак. @@L1: CMP BYTE PTR [ESI],0 JE @@L4 CMP BYTE PTR [ESI],32 JE @@L3 ADD ECX,EDX ;Номер параметра. MOV EDX,0 JMP @@L2 @@L3: OR EDX, 1 @@L2: INC ESI JMP @@L1 @@L4: MOV EAX,ECX RET NUMPAR ENDP ;Получить параметр. ;EBX - указывает на буфер, куда будет помещен параметр. ;В буфер помещается строка с нулем на конце. ;EDI - номер параметра. GETPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ;Указатель на строку. XOR ECX,ECX ;Счетчик. MOV EDX,1 ;Признак. @@L1: CMP BYTE PTR [ESI],0 JE @@L4 CMP BYTE PTR [ESI],32 JE @@L3 ADD ECX,EDX ;Номер параметра. MOV EDX,0 JMP @@L2 @@L3: OR EDX,1 @@L2: CMP ECX,EDI JNE @@L5 MOV AL,BYTE PTR [ESI] CMP AL,32 JE @@L5 MOV BYTE PTR [EBX],AL INC EBX @@L5: INC ESI JMP @@L1 @@L4: MOV BYTE PTR [EBX],0 RET GETPAR ENDP _TEXT ENDS END START
Файл pr82_2.asm - динамически подключаемая библиотека, содержащая процедуру-фильтр:
;Динамическая библиотека, содержащая процедуру-фильтр. .386P ;Плоская модель. IFDEF MASM .MODEL FLAT, stdcall ELSE .MODEL FLAT ENDIF PUBLIC HOOK, TOH ;Константы. ;Сообщения, приходящие при открытии динамической библиотеки. DLL_PROCESS_DETACH equ 0 DLL_PROCESS_ATTACH equ 1 DLL_THREAD_ATTACH equ 2 DLL_THREAD_DETACH equ 3 ;Прототипы внешних процедур. IFDEF MASM EXTERN CallNextHookEx@16:NEAR EXTERN MessageBoxA@16:NEAR ELSE EXTERN CallNextHookEx:NEAR EXTERN MessageBoxA:NEAR CallNextHookEx@16 = CallNextHookEx MessageBoxA@16 = MessageBoxA ENDIF ;Директивы компоновщику для подключения библиотек. IFDEF MASM ;Для компоновщика LINK.EXE. includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib ELSE ;Для компоновщика TLINK32.EXE. includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------ ;Сегмент данных. _DATA SEGMENT DWORD PUBLIC USE32 'DATA' HDL DD ? HHOOK DD ? CAP DB "Сообщение фильтра",0 MES DB "Нажат пробел",0 _DATA ENDS ;Сегмент кода. _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; [EBP+10H] резервный параметр. ; [ЕВР+ОСН] причина вызова. ; [ЕВР+8] ;идентификатор DLL-модуля. DLLENTRY: MOV EAX,DWORD PTR [EBP+0CH] CMP EAX,0 JNE D1 ;Закрытие библиотеки. JMP _EXIT D1: CMP EAX,1 JNE _EXIT ;Открытие библиотеки. ;Запомнить идентификатор динамической библиотеки. MOV EDX,DWORD PTR [EBP+08H] MOV HDL,EDX _EXIT: MOV EAX,1 RET 12 ;----------------------------- TOH PROC EXPORT PUSH EBP MOV EBP,ESP MOV EAX,DWORD PTR [EBP+08H] MOV HHOOK,EAX POP EBP RET TOH ENDP ;Процедура фильтра. HOOK PROC EXPORT PUSH EBP MOV EBP,ESP ;Отправить сообщение по цепочке. PUSH DWORD PTR [EBP+010H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+08H] PUSH HHOOK CALL CallNextHookEx@16 ;Проверить, не нажат ли пробел. CMP DWORD PTR [EBP+0CH],32 JNE _EX ;Нажат - выводим сообщение. PUSH 0 ;МВ_ОК PUSH OFFSET CAP PUSH OFFSET MES PUSH 0 ;В окне экрана. CALL MessageBoxA@16 _EX: POP EBP RET HOOK ENDP _TEXT ENDS END DLLENTRY
Результат работы приложения изображен на рисунке 1:
Рис.1. Результат работы приложения
Трансляция динамической библиотеки:
ML /с /coff /DMASM pr82_2.asm LINK /SUBSYSTEM:WINDOWS /DLL pr82_2.obj
TASM32 /ml pr82_2.asm TLINK32 -aa -Tpd pr82_2.obj,,,,pr82_2.def Содержимое файла pr82_2.def: EXPORTS HOOK, TOH
Трансляция программы:
ML /с /coff /DMASM pr82_1.asm rc pr82_1.rc LINK /SUBSYSTEM:WINDOWS pr82_1.obj
TASM32 /ml pr82_1.asm Brcc32 pr82_1.rc TLINK32 -aa pr82_1.obj
При разборе этой программы обратите внимание на роль, которую играет процедура TOH. Заметьте также, что второй и третий параметры процедуры фильтра в точности соответствуют значению аналогичных параметров сообщения WM_KEYDOWN. Кстати, надеюсь, вы понимаете, почему при нажатии клавиши Пробел появляются два сообщения - по одному на нажатие и отпускание.
Со следующего шага мы начнем знакомиться с особенностями использования ассемблера с языками высокого уровня.