Шаг 82.
Фильтры (Hooks)

    На этом шаге мы рассмотрим создание и использование ловушек.

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

    Рассмотрим некоторые средства для работы с фильтрами. Ниже перечислены основные типы фильтров или сообщения.

    Фильтр устанавливается при помощи функции 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. Результат работы приложения

    Трансляция динамической библиотеки:

    Трансляция программы:

    При разборе этой программы обратите внимание на роль, которую играет процедура TOH. Заметьте также, что второй и третий параметры процедуры фильтра в точности соответствуют значению аналогичных параметров сообщения WM_KEYDOWN. Кстати, надеюсь, вы понимаете, почему при нажатии клавиши Пробел появляются два сообщения - по одному на нажатие и отпускание.

    Со следующего шага мы начнем знакомиться с особенностями использования ассемблера с языками высокого уровня.




Предыдущий шаг Содержание Следующий шаг