Большой архив статей, книг, документации по программированию, вебдизайну, компьютерной графике, сетям, операционным системам и многому другому
 
<Добавить в Избранное>    <Сделать стартовой>    <Реклама на сайте>    <Контакты>
  Главная Документация Программы Обои   Экспорт RSS E-Books
 
 

   Программирование -> C++ Builder -> Создание системных ловушек Windows


Создание системных ловушек 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;
   }
((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;
}

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

 

 
Интересное в сети
 
10 новых программ
CodeLobster PHP Edition 3.7.2
WinToFlash 0.7.0008
Free Video to Flash Converter 4.7.24
Total Commander v7.55
aTunes 2.0.1
Process Explorer v12.04
Backup42 v3.0
Predator 2.0.1
FastStone Image Viewer 4.1
Process Lasso 3.70.4
FastStone Image Viewer 4.0
Xion Audio Player 1.0.125
Notepad GNU v.2.2.8.7.7
K-Lite Codec Pack 5.3.0 Full


Наши сервисы
Рассылка новостей. Подпишитесь на рассылку сейчас и вы всегда будете в курсе последних событий в мире информационных технологий.
Новостные информеры. Поставьте наши информеры к себе и у вас на сайте появится дополнительный постоянно обновляемый раздел.
Добавление статей. Если вы являетесь автором статьи или обзора на тему ИТ присылайте материал нам, мы с удовольствием опубликуем его у себя на сайте.
Реклама на сайте. Размещая рекламу у нас, вы получите новых посетителей, которые могут стать вашими клиентами.
 
Это интересно
 

Copyright © CompDoc.Ru
При цитировании и перепечатке ссылка на www.compdoc.ru обязательна. Карта сайта.
 
Rambler's Top100