А.Е. Шевелёв

Создание системных ловушек Windows на Borland C++ Builder 5

 

Прежде чем излагать материал я хочу заметить, что цель данной работы – показать как пишутся ловушки Windows вообще. Подробности, по мере возможности, я буду опускать (их можно найти в поставляемой со средой разработке справке).

Для начала определим, что именно мы хотим сделать.

Цель: написать программу, которая будет вызывающую хранитель экрана при перемещении курсора мыши в правый верхний угол и выдавать звуковой сигнал через встроенный динамик при переключении языка с клавиатуры.

Предполагается, что такая программа должна иметь небольшой размер. Поэтому будем писать её с использованием только WIN API.

 

Понятие ловушки.

Ловушка (hook) – это механизм, который позволяет производить мониторинг сообщений системы и обрабатывать их до того как они достигнут целевой оконной процедуры.

Для обработки сообщений пишется специальная функция (Hook Procedure). Для начала срабатывания ловушки эту функцию следует специальным образом «подключить» к системе.

Если надо отслеживать сообщения всех потоков, а не только текущего, то ловушка должна быть глобальной. В этом случае функция ловушки должна находиться в DLL.

Таким образом, задача разбивается на две части:

1.        Написание DLL c функциями ловушки (их будет две: одна для клавиатуры, другая для мыши).

2.        Написание приложения, которое установит ловушку.

 

Написание 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;

                                                }

                                                if((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;

}

 

После написания этой функции можно смело запускать полностью готовое приложение.

Ссылка на файл

 

Сайт создан в системе uCoz