Создание системных ловушек Windows на Borland C++ Builder 5А.Е. Шевелёв Прежде чем излагать материал я хочу заметить, что цель данной работы — показать как пишутся ловушки Windows вообще. Подробности, по мере возможности, я буду опускать (их можно найти в поставляемой со средой разработке справке). Для начала определим, что именно мы хотим сделать. Цель: написать программу, которая будет вызывать хранитель экрана при перемещении курсора мыши в правый верхний угол и выдавать звуковой сигнал через встроенный динамик при переключении языка с клавиатуры. Предполагается, что такая программа должна иметь небольшой размер. Поэтому будем писать её с использованием только WIN API. Понятие ловушки.Ловушка (hook) — это механизм, который позволяет производить мониторинг сообщений системы и обрабатывать их до того как они достигнут целевой оконной процедуры. Для обработки сообщений пишется специальная функция (Hook Procedure). Для начала срабатывания ловушки эту функцию следует специальным образом "подключить" к системе. Если надо отслеживать сообщения всех потоков, а не только текущего, то ловушка должна быть глобальной. В этом случае функция ловушки должна находиться в DLL. Таким образом, задача разбивается на две части:
Написание 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 && Звуковой сигнал выдаётся такой небольшой функцией: 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; } После написания этой функции можно смело запускать полностью готовое приложение. |