Операционная система Microsoft Windows 3.1 для программиста -том 3

         

Файл winhook/kbhook.cpp


// ================================================ // DLL-библиотека kbhook.dll // Устанавливает перехватчики на сообщения, // поступающие от клавиатуры и на системную // очередь сообщений. // Если нажать подряд 3 раза клавишу <Control>
, // изменится раскладка клавиатуры // ================================================

#define STRICT #include <windows.h>
#include "kbhook.hpp"

// ----------------------------------------------- // Глобальные переменные // -----------------------------------------------

// Идентификатор модуля DLL-библиотеки static HINSTANCE hInst;

// Идентификатор окна приложения, установившего // перехватчики static HWND hwndClient;

// Идентификаторы перехватчиков static HHOOK hhook = 0; static HHOOK hhookMsg = 0;

// Флаг переключения на русскую клавиатуру static BOOL bCyrillic = FALSE;

// Флаг установки перехватчиков static BOOL bHooked = FALSE;

// Счетчик static int nHotKeyCount = 0;

// Массив для записи состояния клавиатуры BYTE aKeyStates[256];

// Указатели на таблицы перекодировки, // которые будут загружены из ресурсов static char far * lpXlatTable; static char far * lpXlatTableCaps;

// Положение ресурсов в файле static HRSRC hResource; static HRSRC hResourceCaps;

// Идентификаторы таблиц перекодировки static HGLOBAL hXlatTable; static HGLOBAL hXlatTableCaps;



// ----------------------------------------------- // Прототипы функций // -----------------------------------------------

extern "C" LRESULT CALLBACK KbHookProc(int code, WPARAM wParam, LPARAM lParam);

extern "C" LRESULT CALLBACK MsgHookProc(int code, WPARAM wParam, LPARAM lParam);

// ======================================================== // Функция LibMain // Получает управление только один раз при // загрузке DLL-библиотеки в память // ========================================================

#pragma argsused int CALLBACK LibMain(HINSTANCE hInstance, WORD wDataSegment, WORD wHeapSize, LPSTR lpszCmdLine) { // После инициализации локальной области данных // функция LibEntry фиксирует сегмент данных. // Его необходимо расфиксировать.


if(wHeapSize != 0) // Расфиксируем сегмент данных UnlockData(0);

// Запоминаем идентификатор модуля DLL-библиотеки hInst = hInstance;

// Определяем расположение ресурсов hResource = FindResource(hInstance, "XlatTable", "XLAT");
hResourceCaps = FindResource(hInstance, "XlatTableCaps", "XLAT");

// Получаем идентификаторы ресурсов hXlatTable = LoadResource(hInstance, hResource);
hXlatTableCaps = LoadResource(hInstance, hResourceCaps);

// Фиксируем ресурсы в памяти, получая их адрес lpXlatTable = (char far *)LockResource(hXlatTable);
lpXlatTableCaps = (char far *)LockResource(hXlatTableCaps);

// Если адрес равен NULL, при загрузке или // фиксации одного из ресурсов произошла ошибка if(lpXlatTable == NULL lpXlatTableCaps == NULL) { return(0);
}

// Выключаем клавишу <Caps Lock>
. Для этого // получаем и записываем в массив aKeyStates состояние // клавиатуры, затем изменяем состояние нужной нам // клавиши, используя ее виртуальный код как индекс GetKeyboardState(aKeyStates);
aKeyStates[VK_CAPITAL] = 0; SetKeyboardState(aKeyStates);

// Возвращаем 1. Это означает, что инициализация // DLL-библиотеки выполнена успешно return 1; }

// ======================================================== // Функция WEP // Получает управление только один раз при // удалении DLL-библиотеки из памяти // ========================================================

#pragma argsused int CALLBACK WEP(int bSystemExit) { // Расфиксируем и освобождаем ресурсы UnlockResource(hXlatTable);
FreeResource(hXlatTable);
UnlockResource(hXlatTableCaps);
FreeResource(hXlatTableCaps);

return 1; }

// ======================================================== // Функция SetKbHook // Устанавливает системные перехватчики на сообщения, // поступающие от клавиатуры, и на системную // очередь сообщений // ========================================================

extern "C" void WINAPI _export SetKbHook(HWND hwnd) { // Устанавливаем перехватчики только // в том случае, если они не были // установлены ранее if(!bHooked) { // Установка перехватчика на сообщения, // поступающие от клавиатуры hhook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KbHookProc, hInst, NULL);



// Установка перехватчика на системную // очередь сообщений hhookMsg = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)MsgHookProc, hInst, NULL);

// Включаем флаг установки перехватчиков bHooked = TRUE;

// Сохраняем идентификатор окна приложения, // установившего перехватчики hwndClient = hwnd; } }

// ======================================================== // Функция RemoveKbHook // Удаляет системные перехватчики // ========================================================

extern "C" void WINAPI _export RemoveKbHook(void) { // Если перехватчики были установлены, // удаляем их if(bHooked) { UnhookWindowsHookEx(hhookMsg);
UnhookWindowsHookEx(hhook);
} }

// ======================================================== // Функция KbHookProc // Перехватчик сообщений от клавиатуры // ========================================================

extern "C" LRESULT CALLBACK KbHookProc(int code, WPARAM wParam, LPARAM lParam) { // Проверка флага обработки сообщений. Если содержимое // параметра code меньше нуля, передаем сообщение // функции CallNextHookEx без изменений if(code < 0) { CallNextHookEx(hhook, code, wParam, lParam);
return 0; }

// Если пришло сообщение от клавиши <Control>
, // проверяем, была ли эта клавиша нажата // три раза подряд if(wParam == VK_CONTROL) { if(!(HIWORD(lParam) & 0x8000)) { nHotKeyCount++;

// Если клавиша <Control>
была нажата три // раза подряд, инвертируем флаг bCyrillic и посылаем // сообщение приложению, использующему // данную DLL-библиотеку if(nHotKeyCount == 3) { nHotKeyCount = 0; bCyrillic = ~bCyrillic;

// Посылаем сообщение приложению, установившему // перехватчики. В качестве параметра wParam // сообщения передаем значение флага bCyrillic PostMessage(hwndClient, WM_KBHOOK, (WPARAM)bCyrillic, 0L);
} } }

// Если после клавиши <Control>
была нажата любая // другая клавиша, сбрасываем счетчик else { nHotKeyCount = 0; }

// Вызываем следующий в цепочке перехватчик return CallNextHookEx(hhook, code, wParam, lParam);
}



// ======================================================== // Функция MsgHookProc // Перехватчик для системной очереди сообщений // ========================================================

extern "C" LRESULT CALLBACK MsgHookProc(int code, WPARAM wParam, LPARAM lParam) { LPMSG lpmsg; WPARAM wMsgParam;

// Проверка флага обработки сообщений. Если содержимое // параметра code меньше нуля, передаем сообщение // функции CallNextHookEx без изменений if(code < 0) { CallNextHookEx(hhook, code, wParam, lParam);
return 0; }

// Получаем указатель на структуру MSG, // в которой находится перехваченное сообщение lpmsg = (LPMSG)lParam;

// Запоминаем виртуальный код клавиши wMsgParam = lpmsg->
wParam;

// Сбрасываем флаг замены сообщения // WM_KEYDOWN на сообщение WM_CHAR BOOL bChange = FALSE;

// Если перехвачено сообщение WM_KEYDOWN, // проверяем код виртуальной клавиши и при // необходимости выполняем замену сообщения if(lpmsg->
message == WM_KEYDOWN) { // Замена выполняется только в режиме // русской клавиатуры if(bCyrillic) { // Если нажата клавиша, соответствующая // русской букве, включаем флаг bChange switch(wMsgParam) { // Проверяем "особые" буквы case 0xdb: // "Х" case 0xdd: // "Ъ" case 0xba: // "Ж" case 0xde: // "Э" case 0xbc: // "Б" case 0xbe: // "Ю" case 0xbf: // "Ў" { bChange = TRUE; break; }

// Проверяем остальные буквы default: { if((lpmsg->
wParam <= 0x5d && lpmsg->
wParam >
0x2f)) bChange = TRUE; } }

// Если нажата клавиша, соответствующая русской // букве, выполняем замену сообщения WM_KEYDOWN на // сообщение WM_CHAR if(bChange) { // Делаем замену кода сообщения lpmsg->
message = WM_CHAR;

// Необходимо учитывать состояние клавиш // <Caps Lock>
и <Shift>
if(GetKeyState(VK_CAPITAL) & 0x1) { if(GetKeyState(VK_SHIFT) & 0x8000) { // Перекодировка по таблице строчных букв lpmsg->
wParam = lpXlatTable[(lpmsg->
wParam) & 0xff]; } else { // Перекодировка по таблице прописных букв lpmsg->
wParam = lpXlatTableCaps[(lpmsg->
wParam) & 0xff]; } } else { if(GetKeyState(VK_SHIFT) & 0x8000) { // Перекодировка по таблице прописных букв lpmsg->
wParam = lpXlatTableCaps[(lpmsg->
wParam) & 0xff]; } else { // Перекодировка по таблице строчных букв lpmsg->
wParam = lpXlatTable[(lpmsg->
wParam) & 0xff]; } }



// Сбрасываем флаг замены bChange = FALSE; } } }

// Передаем управление следующему в цепочке // перехватчику сообщений CallNextHookEx(hhook, code, wParam, lParam);

return 0; }

Функция LibMain, выполняющая инициализацию DLL-библиотеки, после инициализации сегмента данных, запоминает идентификатор модуля DLL-библиотеки, переданный ей через параметр hInstance, в глобальной переменной hinst.

Затем эта функция находит, загружает в память и фиксирует две таблицы, необходимые для использования дополнительной раскладки клавиатуры. Первая таблица загружается из ресурса "XlatTable". Она содержит таблицу перекодировки кодов виртуальных клавиш в ANSI-коды русских строчных букв. Вторая таблица загружается из ресурса "XlatTableCaps" и содержит таблицу перекодировки кодов виртуальных клавиш в ANSI-коды русских прописных букв.

После загрузки и фиксирования адреса таблиц записываются в глобальные переменные с именами lpXlatTable и lpXlatTableCaps.

Так как перекодировка кодов виртуальных клавиш в ANSI-коды должна выполняться с учетом состояния клавиши <Caps Lock>
, для упрощения перекодировки функция LibMain устанавливает эту клавишу в выключенное состояние, пользуясь функциями GetKeyboardState и SetKeyboardState :

GetKeyboardState(aKeyStates);
aKeyStates[VK_CAPITAL] = 0; SetKeyboardState(aKeyStates);

Указанные функции, а также использованный способ изменения состояния клавиш был описан в 11 томе "Библиотеки системного программиста".

Функция WEP выполняет расфиксирование и освобождение ресурсов, вызывая функции UnlockResource и FreeResource.

Для установки фильтров в нашей DLL-библиотеки определена функция SetKbHook:

extern "C" void WINAPI _export SetKbHook(HWND hwnd) { if(!bHooked) { hhook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KbHookProc, hInst, NULL);
hhookMsg = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)MsgHookProc, hInst, NULL);
bHooked = TRUE; hwndClient = hwnd; }

При определении этой функции мы использовали ключевые слова extern "C", в результате чего транслятор C++ не выполняет преобразование имени функции в соответствии с типом ее параметров и типом возвращаемого значения.



Перед установкой фильтров функция проверяет содержимое глобального флага bHooked. Этот флаг изначально находится в сброшенном состоянии и устанавливается только после встраивания фильтров. Проверка флага позволяет исключить ошибочное повторное встраивание фильтров.

Функция SetKbHook устанавливает два фильтра - типа WH_KEYBOARD и типа WH_GETMESSAGE. Оба фильтра встраиваются для всей системы в целом, так как последний параметр функции SetWindowsHookEx указан как NULL.

После встраивания фильтров устанавливается флаг bHooked, в глобальную переменную hwndClient записывается идентификатор окна, переданного функции SetKbHook в качестве параметра. Приложение WINHOOK передает идентификатор своего главного окна, пользуясь которым фильтр, расположенный в DLL-библиотеке, будет посылать приложению WINHOOK сообщение WM_KBHOOK.

Для удаления фильтров в DLL-библиотеке определена функция RemoveKbHook:

extern "C" void WINAPI _export RemoveKbHook(void) { if(bHooked) { UnhookWindowsHookEx(hhookMsg);
UnhookWindowsHookEx(hhook);
} }

Эта функция проверяет состояние флага bHooked, и, если фильтры были установлены, удаляет их, вызывая для каждого фильтра функцию UnhookWindowsHookEx.

Функция фильтра типа WH_KEYBOARD пропускает через себя все клавиатурные сообщения, определяя момент, когда пользователь нажмет подряд три раза клавишу <Control>
.

Как только это произошло, функция фильтра инвертирует флаг bCyrillic и посылает приложению WINHOOK сообщение WM_KBHOOK. В качестве параметра wParam посылаемого сообщения используется значение флага bCyrillic, что позволяет определить приложению номер используемой раскладки клавиатуры:

PostMessage(hwndClient, WM_KBHOOK, (WPARAM)bCyrillic, 0L);

Фильтр типа WH_MSGFILTER расположен в функции MsgHookProc.

Если перехвачено сообщение WM_KEYDOWN, фильтр проверяет номер используемой раскладки клавиатуры. Если используется стандартная раскладка клавиатуры, сообщение "пропускается" через фильтр без изменений. Для дополнительной раскладки клавиатуры выполняется проверка кода виртуальной клавиши.

Если этот код соответствует клавишам русских букв или цифр в верхнем ряду клавиатуры, фильтр преобразует сообщение WM_KEYDOWN в сообщение WM_CHAR, пользуясь таблицами перекодировок, загруженным из ресурсов DLL-библиотеки. При этом учитывается состояние клавиш <Shift>
и <Caps Lock>
. Для определения состояния переключающих клавиш мы использовали функцию GetKeyState.

Таблицы перекодировки для прописных и строчных букв описаны в файле ресурсов DLL-библиотеки (листинг 3.13).


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