Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

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

© Александр Фролов, Григорий Фролов
Том 13, М.: Диалог-МИФИ, 1993, 284 стр.

[Назад] [Содеожание] [Дальше]

3.7. Приложение WINHOOK

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

Для того чтобы Windows стала "понимать" русские символы, необходимо решить несколько задач.

Во-первых, клавиатура персонального компьютера IBM PC не предназначена для ввода символов кириллицы. В операционной системе MS-DOS эта проблема решалась с помощью резидентных программ, перехватывающих аппаратное прерывание от клавиатуры и заменяющих коды символов. Для операционной системы Microsoft Windows этот способ непригоден, так как для работы с клавиатурой используется специальный драйвер.

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

В-третьих, необходимо обеспечить правильную работу таких функций, как OemToAnsi и AnsiToOem. Эти функции должны выполнять преобразования, учитывая кодировку символов, которая сама по себе зависит от национального языка.

Есть и другие проблемы использования кириллицы как во всей системе в целом, так и в отдельных приложениях, например, обеспечение правильного формата даты и времени, сортировка, замена строчных букв на прописные, проверка грамматики и правописания, вставка переносов и т. д. Короче говоря, проблема работы с кириллицей и русским языком в Windows сложнее, чем может показаться на первый взгляд.

В этом разделе мы приведем исходные тексты приложения WINHOOK, которое решает первую из перечисленных выше задач, т. е. обеспечивает ввод с клавиатуры символов кириллицы. Приложение WINHOOK позволяет вам изменять раскладку клавиатуры с используемой по умолчанию на определенную в ресурсах приложения. Переключение раскладки выполняется при помощи левой или правой клавиши <Control>. Сразу после запуска приложения включается стандартная раскладка клавиатуры. Если вы нажмете на клавишу <Control> три раза, то услышите звуковой сигнал, после чего раскладка клавиатуры меняется на альтернативную и вы можете вводить символы кириллицы.

Приложение WINHOOK создает "непотопляемое" окно, которое располагается над поверхностью любого другого окна в Windows. Вы можете перемещать это окно мышью или изменять его размеры. В зависимости от используемой раскладки клавиатуры в окне отображается одно из двух слов: DEFAULT для стандартной раскладки и CYRILLIC для дополнительной (рис. 3.8).

Рис. 3.8. Главное окно приложения WINHOOK при использовании различных раскладок клавиатуры

Если для работы с кириллицей вы используете такие приложения, как CyrWin или ParaWin, перед запуском приложения WINHOOK их следует либо завершить, либо переключить раскладку клавиатуры на стандартную.

Для того чтобы завершить работу приложения WINHOOK, надо сделать его окно текущим, щелкнув по нему левой клавишей мыши, а затем нажать комбинацию клавиш <Alt + F4>.

Основной файл приложения WINHOOK приведен в листинге 3.9.


Листинг 3.9. Файл winhook/winhook.cpp


// ================================================
// Приложение WINHOOK
// Простейший руссификатор клавиатуры
// для Microsoft Windows
// Работает совместно с DLL-библиотекой kbhook.dll
// ================================================

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <dos.h>
#include <mem.h>

#include "kbhook.hpp"

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

extern "C"
void WINAPI _export SetKbHook(HWND hwnd);

extern "C"
void WINAPI _export RemoveKbHook(void);

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

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

char const szClassName[]   = "WINHOOKAppClass";
char const szWindowTitle[] = "WINHOOK Application";

TEXTMETRIC tm;
int cxChar, cyChar;
RECT rc;
static BOOL bCyrillic = FALSE;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine,
        int       nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Можно запускать только одну копию приложения
  if(hPrevInstance)
    return 0;

  // Инициализация копии приложения
  if(!InitApp(hInstance))
      return FALSE;

  // Получаем координаты окна Desktop.
  // Это окно занимает всю поверхность экрана
  // и на нем расположены все остальные окна
  GetWindowRect(GetDesktopWindow(), &rc);

  // Создаем временное окно с толстой
  // рамкой для изменения размера, но без
  // заголовка и системного меню.
  // При создании окна указываем произвольные
  // размеры окна и произвольное расположение
  hwnd = CreateWindow(
    szClassName, szWindowTitle, 
    WS_POPUPWINDOW | WS_THICKFRAME,
    100, 100,
    100, 100,
    0, 0, hInstance, NULL); 

  if(!hwnd)
    return FALSE;

  // Передвигаем окно в правый нижний
  // угол экрана и делаем его самым
  // верхним, т. е. это окно будет
  // всегда находиться над другими окнами
  SetWindowPos(hwnd, HWND_TOPMOST,
    rc.right  - cxChar * 15,
    rc.bottom - cyChar * 3,
    cxChar * 10, cyChar * 2, 0);

  // Отображаем окно в новом месте
  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна

  memset(&wc, 0, sizeof(wc));
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = (WNDPROC) WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon   = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName  = (LPSTR)NULL;
  wc.lpszClassName = (LPSTR)szClassName;

  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}

// =====================================
// Функция WndProc
// =====================================

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC        hdc;
  PAINTSTRUCT ps;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Устанавливаем перехватчики
      SetKbHook(hwnd);

      // Получаем контекст отображения
      hdc = GetDC(hwnd);

      // Выбираем шрифт с фиксированной шириной букв
      SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

      // Заполняем структуру информацией
      // о метрике шрифта, выбранного в
      // контекст отображения
      GetTextMetrics(hdc, &tm);

      // Запоминаем значение ширины для
      // самого широкого символа
      cxChar = tm.tmMaxCharWidth;

      // Запоминаем значение высоты букв с
      // учетом межстрочного интервала
      cyChar = tm.tmHeight + tm.tmExternalLeading;

      ReleaseDC(hwnd, hdc);
      return 0;
    }

    // Для обеспечения возможности перемещения
    // окна, не имеющего заголовка, встраиваем
    // свой обработчик сообщения WM_NCHITTEST
    case WM_NCHITTEST:
    {
      long lRetVal;

      // Вызываем функцию DefWindowProc и проверяем
      // возвращаемое ей значение
      lRetVal = DefWindowProc(hwnd, msg, wParam, lParam);

      // Если курсор мыши находится на одном из
      // элементов толстой рамки, предназначенной
      // для изменения размера окна, возвращаем
      // неизмененное значение, полученное от
      // функции DefWindowProc
      if(lRetVal == HTLEFT        ||
         lRetVal == HTRIGHT       ||
         lRetVal == HTTOP         ||
         lRetVal == HTBOTTOM      ||
         lRetVal == HTBOTTOMRIGHT ||
         lRetVal == HTTOPRIGHT    ||
         lRetVal == HTTOPLEFT     ||
         lRetVal == HTBOTTOMLEFT)
      {
        return lRetVal;
      }

      // В противном случае возвращаем значение
      // HTCAPTION, которое соответствует
      // заголовку окна.
      else
      {
        return HTCAPTION;
      }
    }

    case WM_DESTROY:
    {
      // Перед завершением работы приложения
      // удаляем перехватчики
      RemoveKbHook();

      PostQuitMessage(0);
      return 0;
    }

    // Это сообщение приходит от DLL-библиотеки
    // при переключении раскладки клавиатуры
    case WM_KBHOOK:
    {
      // Получаем флаг раскладки
      bCyrillic = (BOOL)wParam;

      // Выдаем звуковой сигнал
      MessageBeep(0);

      // Перерисовываем окно приложения
      InvalidateRect(hwnd, NULL, FALSE);
      return 0;
    }

    case WM_PAINT:
    {
      BYTE   szBuf[10];
      RECT    rc;

      // Получаем контекст отображения
      hdc = BeginPaint(hwnd, &ps);

      // Выбираем шрифт с фиксированной шириной букв
      SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

      // Получаем координаты и размер окна
      GetClientRect(hwnd, &rc);

      // В зависимости от состояния флага раскладки
      // клавиатуры выбираем надпись для
      // отображения в окне
      if(bCyrillic)
        lstrcpy(szBuf, (LPCSTR)"CYRILLIC");
      else
        lstrcpy(szBuf, (LPCSTR)"DEFAULT ");

      // Выводим надпись в центре окна
      DrawText(hdc, (LPSTR)szBuf, 8, &rc,
        DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_SINGLELINE);

      EndPaint(hwnd, &ps);
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

После запуска приложения функция WinMain определяет размеры окна DeskTop, которые равны размеру экрана, и создает главное окно приложения в виде временного окна с толстой рамкой для изменения размера без заголовка и системного меню (такое окно имеет стиль WS_POPUPWINDOW | WS_THICKFRAME).

Для того чтобы сделать окно "непотопляемым", а заодно и изменить его размеры, мы вызываем функцию SetWindowPos , передав ей в качестве второго параметра константу HWND_TOPMOST:

SetWindowPos(hwnd, HWND_TOPMOST,
   rc.right  - cxChar * 15,
   rc.bottom - cyChar * 3,
   cxChar * 10, cyChar * 2, 0);

Функция SetWindowPos позволяет изменить размеры и расположение окна относительно экрана и относительно других окон:

BOOL WINAPI SetWindowPos(
  HWND hwnd,            // идентификатор окна
  HWND hwndInsertAfter, // расположение окна
  int x,                // горизонтальное положение 
  int y,                // вертикальное положение
  int cx,               // ширина
  int cy,               // высота
  UINT fuFlags);        // флаги расположения окна

Для параметра hwndInsertAfter, определяющего расположение окна относительно других окон, можно использовать следующие значения:

Значение Описание
HWND_BOTTOM Окно следует расположить под другими окнами
HWND_TOP Окно будет расположено над другими окнами
HWND_TOPMOST Окно следует расположить над всеми другими окнами, имеющими расположение HWND_TOPMOST
HWND_NOTOPMOST Окно будет расположено над всеми HWND_TOP-окнами, но под окном, имеющим расположение HWND_TOPMOST

Параметры x, y, cx и cy определяют, соответственно, горизонтальное и вертикальное расположение окна, его ширину и высоту.

Параметр fuFlags может принимать следующие значения:

Значение Описание
SWP_DRAWFRAME Следует нарисовать рамку, определенную в классе окна
SWP_HIDEWINDOW Окно будет скрыто
SWP_NOACTIVATE Окно не будет активизировано
SWP_NOMOVE Окно не будет перемещаться, при указании этого флага параметры x и y игнорируются
SWP_NOSIZE Окно не будет изменять свои размеры, параметры cx и cy игнорируются
SWP_NOREDRAW Не следует выполнять перерисовку окна. После перемещения приложение должно перерисовать окно самостоятельно
SWP_NOZORDER Не следует изменять расположение окна относительно других окон, параметр hwndInsertAfter игнорируется
SWP_SHOWWINDOW Отобразить окно

После перемещения и изменения расположения главного окна функция WinMain приложения WINHOOK отображает окно и запускает цикл обработки сообщений.

Функция главного окна приложения во время обработки сообщения WM_CREATE вызывает функцию SetKbHook, которая определена в созданной нами для этого приложения DLL-библиотеке kbhook.dll (исходные тексты этой библиотеки будут приведены ниже). Функция SetKbHook устанавливает два фильтра - типа WH_KEYBOARD и WH_GETMESSAGE. В качестве параметра этой функции передается идентификатор главного окна приложения WINHOOK. Когда пользователь переключает раскладку клавиатуры, DLL-библиотека, пользуясь этим идентификатором, пришлет в функцию окна приложения WINHOOK сообщение с кодом WM_KBHOOK.

Затем обработчик сообщения WM_CREATE получает контекст отображения и определяет метрику системного шрифта с фиксированной шириной букв, который будет использоваться для отображения используемой раскладки клавиатуры в главном окне приложения.

В функции главного окна предусмотрен обработчик сообщения WM_NCHITTEST, позволяющий перемещать окно, не имеющее заголовка. Мы уже использовали аналогичный прием в приложении TMCLOCK, описанном в 11 томе "Библиотеки системного программиста".

Перед завершением работы приложение WINHOOK удаляет установленные ранее фильтры, для чего при обработке сообщения WM_DESTROY вызывается функция RemoveKbHook, определенная в DLL-библиотеке kbhook.dll.

Обработчик сообщения WM_KBHOOK, определенного в нашем приложении и в DLL-библиотеке, определяет номер используемой раскладки клавиатуры через параметр wParam. Полученное значение записывается в переменную флагов раскладки bCyrillic. Если этот флаг сброшен, используется стандартная раскладка клавиатуры, если установлен - дополнительная с символами кириллицы.

После определения раскладки обработчик сообщения WM_KBHOOK выдает звуковой сигнал и перерисовывает главное окно приложения, вызывая функцию InvalidateRect.

Перерисовку окна выполняет обработчик сообщения WM_PAINT. С помощью функции DrawText он пишет название раскладки клавиатуры (DEFAULT или CYRILLIC) в центре главного окна приложения.

Сообщение WM_KBHOOK определено в include-файле winhook.hpp (листинг 3.10).


Листинг 3.10. Файл winhook/winhook.hpp


#define WM_KBHOOK (WM_USER + 1000)

Код этого сообщения получается при помощи сложения константы WM_USER и числа 1000 (вы можете выбрать другое число в диапазоне от 0 до 0x7fff).

Константа WM_USER специально предназначена для определения приложениями своих собственных кодов сообщений. Если приложение определит свое собственное сообщение и его код будет находиться в пределах от WM_USER до WM_USER + 0x7fff, код такого сообщения не будет конфликтовать с кодами сообщений операционной системы Windows.

Файл определения модуля приложения WINHOOK приведен в листинге 3.11.


Листинг 3.11. Файл winhook/winhook.def


; =============================
; Файл определения модуля
; =============================
NAME        WINHOOK
DESCRIPTION 'Приложение WINHOOK, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   8120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

Теперь займемся DLL-библиотекой kbhook.dll, предназначенной для совместной работы с приложением WINHOOK. Исходный текст этой библиотеки представлен в листинге 3.12.


Листинг 3.12. Файл 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).


Листинг 3.13. Файл winhook/kbhook.rc


/* Таблица перекодировки */

XlatTableCaps XLAT 
BEGIN
'00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F'
'10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F'
'20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F'
'25 21 2D 2F 22 3A 2C 2E AD 3F 3A 3B 3C 3D 3E 3F'
'40 D4 C8 D1 C2 D3 C0 CF D0 D8 CE CB C4 DC D2 D9'
'C7 C9 CA DB C5 C3 CC D6 D7 CD DF 5B 5C 5D 5E 5F'
'60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F'
'70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F'
'80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F'
'90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F'
'A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF'
'B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 C6 BB C1 BD DE A1'
'C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF'
'D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA D5 DC DA DD DF'
'E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF'
'F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF'

END

XlatTable XLAT
BEGIN
'00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F'
'10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F'
'20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F'
'30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F'
'40 F4 E8 F1 E2 F3 E0 EF F0 F8 EE EB E4 FC F2 F9'
'E7 E9 EA FB E5 E3 EC F6 F7 ED FF 5B 5C 5D 5E 5F'
'60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F'
'70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F'
'80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F'
'90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F'
'A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF'
'B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 E6 BB E1 BD FE BF'
'C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF'
'D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA F5 DC FA FD DF'
'E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF'
'F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF'

END

Файл определения модуля DLL-библиотеки приведен в листинге 3.14.


Листинг 3.14. Файл winhook/kbhook.def


; =============================
; Файл определения модуля
; =============================
LIBRARY        KBHOOK
DESCRIPTION    'DLL-библиотека KBHOOK, (C) 1994, Frolov A.V.'
EXETYPE        windows
CODE           preload fixed
DATA           preload moveable single
HEAPSIZE       1024
EXPORTS
  SetKbHook    @10
  RemoveKbHook @11
  KbHookProc   @12
  MsgHookProc  @13

С помощью этого файла экспортируются функции SetKbHook и RemoveKbHook, предназначенные, соответственно, для установки и удаления фильтров, а также функции фильтров KbHookProc и MsgHookProc.

[Назад] [Содеожание] [Дальше]