Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 13, М.: Диалог-МИФИ, 1993, 284 стр. 3.6. ФильтрыВ операционной системе Windows, в основе которой лежит механизм обмена сообщениями, имеется очень мощное средство, позволяющее организовать фильтрацию или перехват любых сообщений. Причем можно фильтровать сообщения, предназначенные как для некоторых, так и для всех приложений. Вы можете создать свой фильтр, получающий управление до того, как то или иное сообщение достигнет очереди сообщений или перед вызовом соответствующей функции окна. Фильтр может оставить перехваченное сообщение без изменения, изменить его или даже изъять. Можно встроить несколько однотипных фильтров. При этом организуется цепочка фильтров. Первым получит управление тот фильтр, который был встроен в последнюю очередь. Для чего можно использовать фильтры? С помощью фильтров вы можете перехватить сообщения, поступающие в любые диалоговые панели, полосы просмотра, меню и т. п., причем как для отдельного приложения, так и для всех приложений сразу. Можно "отобрать" сообщения у функций GetMessage, PeekMessage, SendMessage. Можно записывать и затем проигрывать события, связанные с перемещением мыши и использованием клавиатуры. Фильтры облегчают создание обучающих приложений, которые работают совместно с обычными приложениями Windows в режиме обучения. Обучающие приложения может контролировать действия пользователя, когда он работает с приложением. В качестве примера в разделе "Приложение WINHOOK" мы расскажем вам о том, как можно с помощью фильтров переключать раскладку клавиатуры. Мы приведем исходные тексты несложного руссификатора клавиатуры, позволяющего использовать в операционной системе Windows символы кириллицы. Установка фильтраДля установки фильтра в операционной системе Windows версии 3.1 следует использовать функцию SetWindowsHookEx (в Windows версии 3.0 для этой цели была предназначена функция SetWindowsHook ): HHOOK WINAPI SetWindowsHookEx( int idHook, // тип фильтра HOOKPROC lpfn, // адрес функции фильтра HINSTANCE hInstance, // идентификатор приложения HTASK hTask); // задача, для которой устанавливается фильтр Параметр idHook определяет тип встраиваемого фильтра. В качестве значения для этого параметра можно использовать одну из следующих констант:
Параметр lpfn функции SetWindowsHookEx должен определять адрес функции встраиваемого фильтра (или, иными словами, функции перехвата сообщений). Функция фильтра может находиться либо в приложении, устанавливающем фильтр, либо (что значительно лучше) в DLL-библиотеке. Если функция находится в приложении, или в DLL-библиотеке, загружаемой явным образом при помощи функции LoadLibrary, в качестве параметра lpfn следует использовать значение, полученное от функции MakeProcInstance. Если же для импортирования функций из DLL-библиотеки используется библиотека импорта (как в описанном ниже приложении WINHOOK), параметр lpfn может содержать непосредственный указатель на функцию фильтра. Через параметр hInstance функции SetWindowsHookEx следует передать идентификатор модуля, в котором находится встраиваемая функция фильтра. Если функция фильтра определена внутри приложения, в качестве этого параметра необходимо использовать идентификатор текущей копии приложения, передаваемой через соответствующий параметр функции WinMain. Если же функция фильтра находится в DLL-библиотеке, данный параметр должен содержать идентификатор модуля библиотеки, передаваемый через параметр hInstance функции LibMain. Последний параметр функции hTask должен содержать идентификатор задачи, для которой определяется функция фильтра. Если последний параметр указан как NULL, фильтр встраивается для всех задач, работающих в системе. Если приложение встраивает фильтр для себя, оно должно определить идентификатор задачи, соответствующий собственной копии приложения. Это проще всего сделать, вызвав функцию GetCurrentTask : HTASK WINAPI GetCurrentTask(void); Можно определить идентификатор задачи исходя из идентификатора окна, созданного этой задачей. Для этого следует воспользоваться функцией GetWindowTask : HTASK WINAPI GetWindowTask(HWND hwnd); Эта функция возвращает идентификатор задачи, создавшей окно с идентификатором hwnd. Функция SetWindowsHookEx возвращает 32-разрядный идентификатор функции фильтра, который следует сохранить для дальнейшего использования, или NULL при ошибке. В приведенном ниже фрагменте кода, взятом из приложения WINHOOK, устанавливается фильтр для сообщений, поступающих во все приложения от клавиатуры: hhook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KbHookProc, hInst, NULL); Исходный текст функции фильтра приведен (с сильными сокращениями) ниже: extern "C" LRESULT CALLBACK KbHookProc(int code, WPARAM wParam, LPARAM lParam) { ... ... ... // Вызываем следующий в цепочке перехватчик return CallNextHookEx(hhook, code, wParam, lParam); } После выполнения всех необходимых действий функция фильтра передает управление по цепочке другим фильтрам (что необязательно). Для этого вызывается функция CallNextHookEx : LRESULT WINAPI CallNextHookEx( HHOOK hHook, int code, WPARAM wParam, LPARAM lParam); Параметр hHook содержит идентификатор текущей функции фильтра. Параметр code содержит код фильтра, который должен передаваться следующему фильтру в цепочке. Параметры wParam и lParam содержат, соответственно, 16- и 32-битовый дополнительные параметры. Параметры code, wParam и lParam функции CallNextHookEx полностью соответствуют параметрам функции фильтра, которая будет рассмотрена нами позже. Отмена фильтраФильтр, установленный функцией SetWindowsHookEx, можно отменить или удалить при помощи функции UnhookWindowsHookEx : BOOL WINAPI UnhookWindowsHookEx(HHOOK hHook); В качестве единственного параметра этой функции следует передать идентификатор функции фильтра, полученный от функции SetWindowsHookEx. Если отмена фильтра выполнена успешно, функция UnhookWindowsHookEx возвращает значение TRUE. В случае возникновения ошибки возвращается FALSE. Функции фильтраФункция фильтра должна иметь следующий прототип: LRESULT CALLBACK HookProc( int code, WPARAM wParam, LPARAM lParam) Имя функции фильтра может быть любым, так как при установке фильтра вы должны указать функции SetWindowsHookEx указатель на функцию фильтра. Параметр code может определять действия, выполняемые функцией фильтра. В операционной системе Windows версии 3.0 этот параметр мог принимать отрицательные значения. В этом случае функция фильтра должна была передавать управление следующему в цепочке фильтру при помощи функции DefHookProc. Операционная система Windows версии 3.1 никогда не вызывает функцию фильтра с отрицательным значением параметра code. Назначение параметров wParam и lParam зависит от типа фильтра, задаваемого при помощи параметра idHook функции SetWindowsHookEx. Мы не будем подробно описывать все возможные типы фильтров, так как это займет очень много времени. При необходимости вы сможете найти полное описание в документации, поставляющейся вместе с SDK. Рассмотрим в деталях только самые интересные, на наш взгляд, типы фильтров. Фильтр WH_CALLWNDPROCПриведем прототип функции фильтра типа WH_CALLWNDPROC (для функции можно использовать любое имя): LRESULT CALLBACK CallWndProc( int code, // код действия WPARAM wParam, // флаг текущей задачи LPARAM lParam) // адрес структуры с сообщением Функция CallWndProc обязательно должна находиться DLL-библиотеке. Она вызывается операционной системой Windows после вызова функции SendMessage, но перед тем, как сообщение попадет в функцию окна, для которой оно предназначено. Фильтр может изменить любые параметры сообщения или его код. Если код действия (параметр code) меньше нуля, функция фильтра должна вызвать функцию CallNextHookEx, не изменяя перехваченное сообщение. Если сообщение послано текущей задачей, значение параметра wParam отлично от нуля, в противном случае оно равно NULL. Параметр lParam содержит указатель на структуру, которая описывает перехваченное сообщение (эта структура на описана в файле windows.h): struct _Msg { LPARAM lParam; // параметр lParam сообщения WPARAM wParam; // параметр wParam сообщения UINT uMsg; // код сообщения HWND hWnd; // идентификатор окна, для которого }; // предназначено сообщение Функция фильтра должна всегда возвращать нулевое значение. Заметим, что после установки фильтра WH_CALLWNDPROC производительность операционной системы Windows заметно уменьшится, поэтому использование такого фильтра оправдано только при отладке приложения или для создания приложений, специально предназначенных для отладки других приложений Windows. Фильтр WH_CBTБольшинство приложений, созданных фирмой Microsoft для своей операционной системы Windows, имеют встроенные обучающие системы, предназначенные для того, чтобы пользователь мог быстрее освоить работу с приложением. Например, текстовый процессор Microsoft Word for Windows версий 2.0 и 6.0 имеет очень удобную и легкую в использовании обучающую систему. Эта обучающая система не только рассказывает пользователю о том, как надо работать с текстовым процессором, но и, что очень важно, дает ему возможность попробовать выполнить те или иные действия самостоятельно. Когда пользователь пытается работать самостоятельно, все выглядит так, как будто он имеет дело с настоящим текстовым процессором, но при этом его действия ограничиваются обучающей системой. Для облегчения создания подобных обучающих программ приложения могут установить фильтр WH_CBT, специально предназначенный для организации контроля за действиями пользователя со стороны обучающих программ. Мы не беремся утверждать, что обучающая программа для текстового процессора Microsoft Word for Windows построена с использованием этого фильтра, однако, вполне вероятно, что это так и есть. Приведем прототип функции фильтра WH_CBT: LRESULT CALLBACK CBTProc( int code, // код действия WPARAM wParam, // назначение зависит от кода действия LPARAM lParam); // назначение зависит от кода действия Данная функция должна располагаться в DLL-библиотеке. Она вызывается операционной системой перед выполнением таких действий, как создание, активизация, удаление, перемещение и изменение размеров окна, перед удалением сообщений мыши или клавиатурных сообщений из системной очереди сообщений, перед передачей фокуса ввода и т. д. Фильтр может разрешить или запретить выполнение перечисленных выше операций, возвращая соответствующее значение. В зависимости от параметра code меняется назначение параметров wParam и lParam. Перечислим возможные значения для параметра code и кратко опишем назначение остальных параметров функции фильтра WH_CBT.
Фильтр WH_DEBUGПриведем прототип функции фильтра типа WH_DEBUG : LRESULT CALLBACK DebugProc( int code, // код действия WPARAM wParam, // идентификатор задачи LPARAM lParam); // адрес структуры DEBUGHOOKINFO Фильтр WH_DEBUG предназначен для отладчиков и должен находиться в DLL-библиотеке. Он вызывается перед вызовом других фильтров, установленных функцией SetWindowsHookEx. Параметр wParam содержит идентификатор задачи, которая установила фильтр. Параметр lParam содержит дальний указатель на структуру DEBUGHOOKINFO : typedef struct tagDEBUGHOOKINFO { HMODULE hModuleHook; LPARAM reserved; LPARAM lParam; WPARAM wParam; int code; } DEBUGHOOKINFO; В этой структуре в поле hModuleHook находится идентификатор модуля, содержащего функцию фильтра, поля lParam, wParam, code содержат параметры, передаваемые функции фильтра. Поле reserved не используется. Функция фильтра типа WH_DEBUG может предотвратить вызов другого фильтра, для чего она должна возвратить значение TRUE. Если она вернет FALSE, управление будет передано соответствующему фильтру. Фильтр WH_GETMESSAGEФильтр WH_GETMESSAGE получает управление, когда функция GetMessage или PeekMessage возвращают выбранное из очереди сообщение. Функция фильтра должна находиться в DLL-библиотеке. Приведем прототип функции фильтра типа WH_GETMESSAGE: LRESULT CALLBACK GetMsgProc( int code, // код действия WPARAM wParam, // не определено LPARAM lParam); // адрес структуры MSG Параметр lParam содержит указатель на структуру MSG , содержащую перехваченное сообщение: typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG; Функция фильтра может изменить любой параметр сообщения и даже его код. В последнем случае произойдет замена одного сообщения на другое. Возвращаемое функцией фильтра значение должно всегда равняться нулю. Фильтр WH_HARDWAREФильтр WH_HARDWARE предназначен для перехвата сообщений, поступающих от нестандартных устройств ввода, таких, как устройства перьевого ввода (клавиатура и мышь - это стандартные устройства ввода). Функция фильтра должна находиться в DLL-библиотеке. Приведем прототип функции фильтра типа WH_HARDWARE: LRESULT CALLBACK HardwareProc( int code, // код действия WPARAM wParam, // не определено LPARAM lParam); // адрес структуры HARDWAREHOOKSTRUCT Структура HARDWAREHOOKSTRUCT описана в файле windows.h: typedef struct tagHARDWAREHOOKSTRUCT { HWND hWnd; UINT wMessage; WPARAM wParam; LPARAM lParam; } HARDWAREHOOKSTRUCT; В этой структуре поле hWnd содержит идентификатор окна, которому предназначено сообщение, поле wMessage содержит код сообщения, поля wParam и lParam содержат дополнительную информацию, зависящую от кода сообщения. Фильтр WH_JOURNALRECORDФильтр WH_JOURNALRECORD вызывается, когда Windows удаляет сообщения из системной очереди. Функция фильтра должна находиться в DLL-библиотеке. Приведем прототип функции фильтра типа WH_JOURNALRECORD: LRESULT CALLBACK JournalRecordProc( int code, // код действия WPARAM wParam, // содержит NULL LPARAM lParam); // адрес структуры EVENTMSG Данный фильтр предназначен для записи перехваченных сообщений в память или файл. Он не может изменять или удалять сообщения из системной очереди. Параметр code может принимать одно из трех значений:
Структура EVENTMSG описана в файле windows.h: typedef struct tagEVENTMSG { UINT message; UINT paramL; UINT paramH; DWORD time; } EVENTMSG; Фильтр WH_JOURNALPLAYBACKФильтр WH_JOURNALPLAYBACK используется для "проигрывания" сообщений, записанных фильтром WH_JOURNALRECORD. Если установлен этот фильтр, обычный ввод при помощи мыши или клавиатуры отключается. Функция фильтра должна находиться в DLL-библиотеке. Приведем прототип функции фильтра типа WH_JOURNALPLAYBACK: LRESULT CALLBACK JournalPlaybackProc( int code, // код действия WPARAM wParam, // содержит NULL LPARAM lParam); // адрес структуры EVENTMSG Перед возвратом управления функция фильтра WH_JOURNALPLAYBACK должна записать по адресу, переданному ей через параметр lParam, структуру данных, записанную ранее функцией фильтра WH_JOURNALRECORD. Функция фильтра должна вернуть интервал времени ожидания (в машинных тиках) перед тем как Windows начнет обработку сообщения. Если вернуть нулевое значение, ожидание выполняться не будет. Фильтр WH_KEYBOARDФильтр WH_KEYBOARD получает управление, когда функции GetMessage или PeekMessage возвращают сообщения WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP или WM_CHAR. Функция фильтра должна находиться в DLL-библиотеке. Приведем прототип функции фильтра типа WH_KEYBOARD: LRESULT CALLBACK KeyboardProc( int code, // код действия WPARAM wParam, // код виртуальной клавиши LPARAM lParam); // дополнительная информация Параметр code может принимать значения HC_ACTION и HC_NOREMOVE. В первом случае перехваченное сообщение после обработки будет удалено из системной очереди сообщений, во втором - останется в этой очереди (т. к. оно было выбрано при помощи функции PeekMessage с параметром PM_NOREMOVE). Если сообщение останется в очереди, таблица состояния клавиатуры, которую можно получить при помощи функции GetKeyboardState, может не отражать состояние клавиатуры на момент выборки сообщения. Параметры wParam и lParam содержат ту же самую информацию, что и соответствующие параметры клавиатурных сообщений WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_CHAR. Параметр wParam содержит код виртуальной клавиши, соответствующей нажатой физической клавише. Именно этот параметр используется приложениями для идентификации нажатой клавиши. Приведем описание отдельных бит парамера lParam, через который передается дополнительная информация о клавиатурном сообщении .
При помощи этого фильтра приложение может удалить клавиатурное сообщение. Для удаления сообщения функция фильтра должна вернуть значение 1. Если же возвращенное значение будет равно 0, сообщение будет обработано системой. Фильтр WH_MOUSEФильтр WH_MOUSE получает управление, когда функции GetMessage или PeekMessage возвращают сообщения мыши. Функция фильтра должна находиться в DLL-библиотеке. Приведем прототип функции фильтра типа WH_MOUSE: LRESULT CALLBACK MouseProc( int code, // код действия WPARAM wParam, // код сообщения LPARAM lParam); // указатель на структуру MOUSEHOOKSTRUCT Так же, как и для предыдущего фильтра, параметр code может принимать значения HC_ACTION и HC_NOREMOVE. Параметр wParam содержит код сообщения, поступившего от мыши. Через параметр lParam передается указатель на структуру MOUSEHOOKSTRUCT : typedef struct tagMOUSEHOOKSTRUCT { POINT pt; HWND hwnd; UINT wHitTestCode; DWORD dwExtraInfo; } MOUSEHOOKSTRUCT; Эта структура содержит дополнительную информацию, имеющую отношение к сообщению. Поле pt является структурой типа POINT, в которой находятся экранные x- и y-координаты курсора мыши. Поле hwnd содержит идентификатор окна, в функцию которого будет направлено сообщение. В поле wHitTestCode находится код тестирования, определяющий область окна, соответствующую расположению курсора мыши на момент генерации сообщения. Поле dwExtraInfo содержит дополнительную информацию, которую можно получить с помощью функции GetMessageExtraInfo : LPARAM WINAPI GetMessageExtraInfo(void); Назначение отдельных бит возвращаемого этой функцией значения зависит от аппаратного обеспечения. Фильтр WH_MSGFILTERФильтр WH_MSGFILTER получает управление, когда диалоговая панель или меню выбирает сообщение. Функции фильтра разрешается изменить или обработать перехваченное сообщение. Фильтр WH_MSGFILTER может находиться в приложении или в DLL-библиотеке, его можно определить как для отдельной задачи, так и для всей системы в целом. Приведем прототип функции фильтра типа WH_MSGFILTER: LRESULT CALLBACK MessageProc( int code, // код действия WPARAM wParam, // не определено LPARAM lParam); // указатель на структуру MSG Параметр code может принимать значения MSGF_DIALOGBOX (ввод в диалоговой панели), MSGF_SCROLLBAR (ввод в области полосы просмотра), MSGF_MENU (ввод в меню) или MSGF_NEXTWINDOW (пользователь активизирует следующее окно, нажимая комбинацию клавиш <Alt + Tab> или <Alt + Esc>). Если фильтр обрабатывает сообщение, функция фильтра должна вернуть ненулевое значение, если нет - нулевое. Фильтр WH_SYSMSGFILTERФильтр WH_SYSMSGFILTER получает управление, когда диалоговая панель или меню выбирает сообщение. Функции фильтра разрешается изменить или обработать перехваченное сообщение. Фильтр WH_SYSMSGFILTER может находиться только DLL-библиотеке. Назначение этого фильтра аналогично назначению фильтра WH_MSGFILTER, но он может быть установлен только для всей системы в целом. Приведем прототип функции фильтра типа WH_SYSMSGFILTER: LRESULT CALLBACK SysMsgProc( int code, // код действия WPARAM wParam, // не определено LPARAM lParam); // указатель на структуру MSG Если фильтр обрабатывает сообщение, функция фильтра должна вернуть ненулевое значение, если нет - нулевое. Приложение может установить одновременно фильтры WH_SYSMSGFILTER и WH_MSGFILTER, в этом случае вначале вызывается фильтр WH_SYSMSGFILTER, а затем - фильтр WH_MSGFILTER. Если же функция фильтра WH_SYSMSGFILTER возвращает ненулевое значение, фильтр WH_MSGFILTER не вызывается. Фильтр WH_SHELLФильтр WH_SHELL предназначен для приложений-оболочек (shell application) и позволяет получать необходимые извещения от операционной системы Windows. Приведем прототип функции фильтра типа WH_SHELL: LRESULT CALLBACK ShellProc( int code, // код действия WPARAM wParam, // флаг текущей задачи LPARAM lParam); // не определено Параметр code может принимать одно из следующих значений:
Функция фильтра должна вернуть нулевое значение. |