Создание системных ловушек Windows на Borland C++ Builder 5
А.Е. Шевелёв
Прежде чем излагать материал я хочу заметить,
что цель данной работы - показать как пишутся ловушки Windows
вообще. Подробности, по мере возможности, я буду опускать (их
можно найти в поставляемой со средой разработке справке).
Для начала определим, что именно мы хотим
сделать.
Цель: написать программу, которая будет
вызывать хранитель экрана при перемещении курсора мыши в
правый верхний угол и выдавать звуковой сигнал через
встроенный динамик при переключении языка с клавиатуры.
Предполагается, что такая программа должна
иметь небольшой размер. Поэтому будем писать её с
использованием только WIN API.
Понятие ловушки.
Ловушка (hook) - это механизм, который
позволяет производить мониторинг сообщений системы и
обрабатывать их до того как они достигнут целевой оконной
процедуры.
Для обработки сообщений пишется специальная
функция (Hook Procedure). Для начала срабатывания
ловушки эту функцию следует специальным образом "подключить" к
системе.
Если надо отслеживать сообщения всех потоков,
а не только текущего, то ловушка должна быть глобальной. В
этом случае функция ловушки должна находиться в DLL.
Таким образом, задача разбивается на две
части:
- Написание DLL c функциями ловушки (их будет две: одна
для клавиатуры, другая для мыши).
- Написание приложения, которое установит ловушку.
Написание DLL.
Создание пустой библиотеки.
С++ Builder имеет встроенный мастер по
созданию DLL. Используем его, чтобы создать пустую библиотеку.
Для этого надо выбрать пункт меню File->New: В появившемся
окне надо выбрать "DLL Wizard" и нажать кнопку "Ok". В новом
диалоге в разделе "Source Type" следует оставить значение по
умолчанию - "C++". Во втором разделе надо снять все флажки.
После нажатия кнопки "Ок" пустая библиотека будет создана.
Глобальные переменные и функция входа
(DllEntryPoint).
Надо определить некоторые глобальные
переменные, которые понадобятся в дальнейшем. #define UP 1// Состояния клавиш
#define DOWN 2
#define RESET 3
int iAltKey; // Здесь хранится состояние клавиш
int iCtrlKey;
int iShiftKey;
int KEYBLAY;// Тип переключения языка
bool bSCRSAVEACTIVE;// Установлен ли ScreenSaver
MOUSEHOOKSTRUCT* psMouseHook; // Для анализа сообшений от мыши
В функции DllEntryPoint надо написать код,
подобный нижеприведённому: if(reason==DLL_PROCESS_ATTACH)// Проецируем на адр. простр.
{
HKEY pOpenKey;
char* cResult=""; // Узнаём как перекл. раскладка
long lSize=2;
KEYBLAY=3;
if(RegOpenKey(HKEY_USERS,".Default\\keyboard layout\\toggle",
&pOpenKey)==ERROR_SUCCESS)
{
RegQueryValue(pOpenKey,"",cResult,&lSize);
if(strcmp(cResult,"1")==0)
KEYBLAY=1; // Alt+Shift
if(strcmp(cResult,"2")==0)
KEYBLAY=2; // Ctrl+Shift
RegCloseKey(pOpenKey);
}
else
MessageBox(0,"Не могу получить данные о способе"
"переключения раскладки клавиатуры",
"Внимание!",MB_ICONERROR);
//------------- Есть ли активный хранитель эрана
if(!SystemParametersInfo(SPI_GETSCREENSAVEACTIVE,0,&bSCRSAVEACTIVE,0))
MessageBox(0,"Не могу получить данные об установленном"
"хранителе экрана", "Внимание!",MB_ICONERROR);
}
return 1;
Этот код позволяет узнать способ переключения
языка и установить факт наличия активного хранителя экрана.
Обратите внимание на то, что этот код выполняется только когда
библиотека проецируется на адресное пространство процесса -
проверяется условие (reason==DLL_PROCESS_ATTACH). Если
вас интересуют подробности, то их можно узнать в разделе
справки "Win32 Programmer's Reference" в подразделе
"DllEntryPoint".
Функция ловушки клавиатуры.
Функция ловушки в общем виде имеет следующий
синтаксис:
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam,
LPARAM lParam), где:
HookProc - имя функции,
nCode - код ловушки, его конкретные значения
определяются типом ловушки,
wParam, lParam - параметры с информацией о
сообщении.
В случае нашей задачи функция должна
определять состояние клавиш Alt, Ctrl и
Shift (нажаты или отпущены). Информация об этом берётся
из параметров wParam и lParam (подробности в
"Win32 Programmer's Reference" в подразделе "KeyboardProc").
После определения состояния клавиш надо сравнить его со
способом переключения языка (определяется в функции входа).
Если текущая комбинация клавиш способна переключить язык, то
надо выдать звуковой сигнал.
Всё это реализует примерно такой код: LRESULT CALLBACK KeyboardHook(int nCode,WPARAM wParam,LPARAM lParam)
{ // Ловушка клав. - биканье при перекл. раскладки
if((lParam>>31)&1) // Если клавиша нажата...
switch(wParam)
{// Определяем какая именно
case VK_SHIFT: {iShiftKey=UP; break};
case VK_CONTROL: {iCtrlKey=UP; break};
case VK_MENU: {iAltKey=UP; break};
}
else// Если была отпущена...
switch(wParam)
{// Определяем какая именно
case VK_SHIFT: {iShiftKey=DOWN; break};
case VK_CONTROL: {iCtrlKey=DOWN; break};
case VK_MENU: {iAltKey=DOWN; break};
}
//--------------
switch(KEYBLAY) // В зависимости от способа переключения раскладки
{
case 1: // Alt+Shift
{
if(iAltKey==DOWN && iShiftKey==UP)
{
vfBeep();
iShiftKey=RESET;
}
if(iAltKey==UP && iShiftKey==DOWN)
{
vfBeep();
iAltKey=RESET;
}
((iAltKey==UP && iShiftKey==RESET)||(iAltKey==RESET &&
iShiftKey==UP))
{
iAltKey=RESET;
iShiftKey=RESET;
}
break;
}
//------------------------------------
case 2: // Ctrl+Shift
{
if(iCtrlKey==DOWN && iShiftKey==UP)
{
vfBeep();
iShiftKey=RESET;
}
if(iCtrlKey==UP && iShiftKey==DOWN)
{
vfBeep();
iCtrlKey=RESET;
}
if((iCtrlKey==UP && iShiftKey==RESET)||(iCtrlKey==RESET &&
iShiftKey==UP))
{
iCtrlKey=RESET;
iShiftKey=RESET;
}
}
}
return 0;
}
Звуковой сигнал выдаётся такой небольшой
функцией: void vfBeep()
{// Биканье
MessageBeep(-1);
MessageBeep(-1);// Два раза - для отчётливости
}
Функция ловушки мыши.
Эта функция отслеживает движение курсора
мыши, получает его координаты и сравнивает их с координатами
правого верхнего угла экрана (0,0). Если эти координаты
совпадают, то вызывается хранитель экрана. Для отслеживания
движения анализируется значение параметра wParam, а для
отслеживания координат значение, находящееся в структуре типа
MOUSEHOOKSTRUCT, на которую указывает lParam
(подробности можно найти в "Win32 Programmer's Reference"
в подразделе "MouseProc"). Код, реализующий вышесказанное,
примерно такой: LRESULT CALLBACK MouseHook(int nCode,WPARAM wParam,LPARAM lParam)
{ // Ловушка мыши - включает хранитель когда в углу
if(wParam==WM_MOUSEMOVE || wParam==WM_NCMOUSEMOVE)
{
psMouseHook=(MOUSEHOOKSTRUCT*)(lParam);
if(psMouseHook->pt.x==0 && psMouseHook->pt.y==0)
if(bSCRSAVEACTIVE)
PostMessage(psMouseHook->hwnd,WM_SYSCOMMAND,
SC_SCREENSAVE,0);
}
return 0;
}
Обратите внимание, что команда на активизацию
хранителя посылается в окно, получающее сообщения от мыши:
PostMessage(psMouseHook->hwnd,WM_SYSCOMMAND,SC_SCREENSAVE
,0).
Теперь, когда функции ловушек написаны, надо
сделать так, чтобы они были доступны из процессов,
подключающих эту библиотеку. Для этого перед функцией входа
следует добавить такой код: extern "C" __declspec(dllexport) LRESULT CALLBACK KeyboardHook(int,WPARAM,LPARAM);
extern "C" __declspec(dllexport) LRESULT CALLBACK MouseHook(int,WPARAM,LPARAM);
Написание приложения, устанавливающего ловушку.
Создание пустого приложения.
Для создания пустого приложения
воспользоваться встроенным мастером. Для этого надо
использовать пункт меню File->New: В появившемся окне
необходимо выбрать "Console Wizard" и нажать кнопку "Ok". В
новом диалоге в разделе "Source Type" следует оставить
значение по умолчанию - "C++". Во втором разделе надо снять
все флажки. По нажатию "Ок" приложение создаётся.
Создание главного окна.
Следующий этап - это создание главного окна
приложения. Сначала надо зарегистрировать класс окна. После
этого создать окно (подробности можно найти в "Win32
Programmer's Reference" в подразделах "RegisterClass" и
"CreateWindow"). Всё это делает следующий код (описатель окна
MainWnd определён глобально): BOOL InitApplication(HINSTANCE hinstance,int nCmdShow)
{ // Создание главного окна
WNDCLASS wcx; // Класс окна
wcx.style=NULL;
wcx.lpfnWndProc=MainWndProc;
wcx.cbClsExtra=0;
wcx.cbWndExtra=0;
wcx.hInstance=hinstance;
wcx.hIcon=LoadIcon(hinstance,"MAINICON");
wcx.hCursor=LoadCursor(NULL,IDC_ARROW);
wcx.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE);
wcx.lpszMenuName=NULL;
wcx.lpszClassName="HookWndClass";
if(RegisterClass(&wcx)) // Регистрируем класс
{
MainWnd=CreateWindow("HookWndClass","SSHook", /* Создаём окно */
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,
NULL,NULL,hinstance,NULL);
if(!MainWnd)
return FALSE;
return TRUE;
}
return false;
}
Обратите внимание на то, каким образом был
получен значок класса:
wcx.hIcon=LoadIcon(hinstance,"MAINICON"); Для того,
чтобы это получилось надо включить в проект файл ресурсов
(*.res), в котором должен находиться значок с именем
"MAINICON".
Это окно никогда не появится на экране,
поэтому оно имеет размеры и координаты, устанавливаемые по
умолчанию. Оконная процедура такого окна необычайно проста: LRESULT CALLBACK MainWndProc(HWND hwnd,UINT uMsg,WPARAM wParam,
LPARAM lParam)
{// Оконная процедура
switch (uMsg)
{
case WM_DESTROY:{PostQuitMessage(0); break;}
//------------
case MYWM_NOTIFY:
{
if(lParam==WM_RBUTTONUP)
PostQuitMessage(0);
break; // Правый щелчок на значке - завершаем
}
default:
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
return 0;
}
Размещение значка в системной области.
Возникает естественный вопрос: если окно
приложения никогда не появится на экране, то каким образом
пользователь может управлять им (например, закрыть)? Для
индикации работы приложения и для управления его работой
поместим значок в системную область панели задач. Делается это
следующей функцией: void vfSetTrayIcon(HINSTANCE hInst)
{ // Значок в Tray
char* pszTip="Хранитель экрана и раскладка";// Это просто Hint
NotIconD.cbSize=sizeof(NOTIFYICONDATA);
NotIconD.hWnd=MainWnd;
NotIconD.uID=IDC_MYICON;
NotIconD.uFlags=NIF_MESSAGE|NIF_ICON|NIF_TIP;
NotIconD.uCallbackMessage=MYWM_NOTIFY;
NotIconD.hIcon=LoadIcon(hInst,"MAINICON");
lstrcpyn(NotIconD.szTip,pszTip,sizeof(NotIconD.szTip));
Shell_NotifyIcon(NIM_ADD,&NotIconD);
}
Для корректной работы функции предварительно нужно
определить уникальный номер значка (параметр
NotIconD.uID) и его сообщение (параметр
NotIconD.uCallbackMessage). Делаем это в области
определения глобальных переменных: #define MYWM_NOTIFY (WM_APP+100)
#define IDC_MYICON 1006
Сообщение значка будет обрабатываться в оконной
процедуре главного окна (NotIconD.hWnd=MainWnd): case MYWM_NOTIFY:
{
if(lParam==WM_RBUTTONUP)
PostQuitMessage(0);
break; // Правый щелчок на значке - завершаем
}
Этот код просто завершает работу приложения
по щелчку правой кнопкой мыши на значке.
При завершении работы значок надо удалить: void vfResetTrayIcon()
{// Удаляем значок
Shell_NotifyIcon(NIM_DELETE,&NotIconD);
}
Установка и снятие ловушек.
Для получения доступа в функциям ловушки надо определить
указатели на эти функции: LRESULT CALLBACK (__stdcall *pKeybHook)(int,WPARAM,LPARAM);
LRESULT CALLBACK (__stdcall *pMouseHook)(int,WPARAM,LPARAM);
После этого спроецируем написанную DLL на адресное
пространство процесса: hLib=LoadLibrary("SSHook.dll");
(hLib описан как HINSTANCE hLib).
После этого мы должны получить доступ к функциям ловушек: (void*)pKeybHook=GetProcAddress(hLib,"KeyboardHook");
(void*)pMouseHook=GetProcAddress(hLib,"MouseHook");
Теперь всё готово к постановке ловушек. Устанавливаются они
с помощью функции SetWindowsHookEx: hKeybHook=SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)(pKeybHook),hLib,0);
hMouseHook=SetWindowsHookEx(WH_MOUSE,(HOOKPROC)(pMouseHook), hLib,0);
(hKeybHook и hMouseHook описаны как HHOOK hKeybHook; HOOK hMouseHook;)
Первый параметр - тип ловушки (в данном
случае первая ловушка для клавиатуры, вторая - для мыши).
Второй - адрес процедуры ловушки. Третий - описатель
DLL-библиотеки. Последний параметр - идентификатор потока, для
которого будет установлена ловушка. Если этот параметр равен
нулю (как в нашем случае), то ловушка устанавливается для всех
потоков.
После установки ловушек они начинают работать. При
завершении работы приложения следует их снять и отключить DLL.
Делается это так: UnhookWindowsHookEx(hKeybHook);
UnhookWindowsHookEx(hMouseHook); // Завершаем
FreeLibrary(hLib);
Функция WinMain.
Последний этап - написание функции WinMain в
которой будет создаваться главное окно, устанавливаться значок
в системную область панели задач, ставиться и сниматься
ловушки. Код её должен быть примерно такой: WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
//----------------
hLib=LoadLibrary("SSHook.dll");
if(hLib)
{
(void*)pKeybHook=GetProcAddress(hLib,"KeyboardHook");
hKeybHook=SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)(pKeybHook),
hLib,0);// Ставим ловушки
(void*)pMouseHook=GetProcAddress(hLib,"MouseHook");
hMouseHook=SetWindowsHookEx(WH_MOUSE,(HOOKPROC)(pMouseHook),
hLib,0);
//-------------------------------
if (InitApplication(hInstance,nCmdShow))// Если создали главное окно
{
vfSetTrayIcon(hInstance);// Установили значок
while (GetMessage(&msg,(HWND)(NULL),0,0))
{// Цикл обработки сообщений
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//---------------------------------- Всё - финал
UnhookWindowsHookEx(hKeybHook); // Снимаем ловушки
UnhookWindowsHookEx(hMouseHook);
FreeLibrary(hLib);// Отключаем DLL
vfResetTrayIcon();// Удаляем значок
return 0;
}
}
return 1;
}
После написания этой функции можно смело запускать
полностью готовое приложение.
|