wParam: WPARAM; //Параметр сообщения
message: UINT; //Код сообщения
hwnd: HWND; //Окно, которому адресовано сообщение
end;
Ниже приводится пример преобразования параметра lParam функции ловушки к указателю на структуру с последующей проверкой кода сообщения (фрагмент программы):
var hook_data : hook_data: ^TCWPStruct;
begin
hook_data := Pointer(lParam);
if hook_data^.message = WM_SIZE then
begin
//Реагируем на изменение размера окна
end;
end;
Получение доступа к данным, передаваемым в остальные функции ловушки (а именно, несложное преобразование типов у операции с указателем), осуществляется аналогичным образом, поэтому более демонстрироваться не будет.
Перехват возврата из оконной процедуры
Для ловушки WH_CALLWNDPROCRET параметры wParam и lParam функции-ловушки следует трактовать следующим образом:
• wParam – равен нулю, если сообщение послано другим процессом, и не равен нулю в противном случае;
• lParam – указатель на структуру TCWPRetStruct, содержащую информацию о сообщении, которое передано окну (и будет передано в оконную функцию).
Объявление структуры TCWPRetStruct с описанием ее полей выглядит следующим образом:
type TCWPRetStruct = packed record
lResult: LRESULT; //Значение, возвращенное оконной функцией
lParam: LPARAM; //Параметр сообщения
wParam: WPARAM; //Параметр сообщения
message: UINT; //Код сообщения
hwnd: HWND; //Дескриптор окна-получателя
end;
Перехват сообщений клавиатурного ввода
Для ловушки WH_KEYBOARD параметры wParam и lParam функции-ловушки следует трактовать следующим образом:
• wParam – код нажатой клавиши;
• lParam – первые 16 бит этого параметра означают количество повторений нажатия; старшие 16 бит используются для дополнительного описания состояния клавиатуры в момент нажатия клавиши.
Параметры wParam и lParam полностью аналогичны параметрам сообщений WM_KEYDOWN И WM_KEYUP.
Перехват сообщений от мыши
В ловушку WH_KEYBOARD в параметрах wParam и lParam передаются следующие значения:
• wParam – код сообщения мыши;
• lParam– указатель на структуру TMouseHookStruct.
Объявление структуры TMouseHookStruct с описанием полей выглядит следующим образом:
type TMouseHookStruct = packed record
pt: TPoint; //Экранные координаты указателя мыши
hwnd: HWND; //Дескриптор окна-получателя сообщения
wHitTestCode: UINT; //Код, возвращенный оконной функцией
//в ответ на сообщение WM_NCHITTEST
dwExtraInfo: DWORD; //Дополнительные данные
end;
Если вы забыли, какое значение имеет для окна coo6nreHHeWM_NCHITTEST, то можете вновь обратиться к гл. 1.
Расположение функции-ловушки и DLL
Теперь поговорим немного о расположении функции-ловушки.
Казалось бы, что здесь может быть такого: написал функцию в модуле, строго соответствующую приведенному ранее прототипу, передал ее адрес в функцию SetWindowsHookEx и используй ловушку. Но не так все просто. Функция ловушки может находиться в исполняемом файле только в том случае, если предполагается использовать ее для перехвата сообщений потока (потоков) того же процесса. Тогда в функцию создания ловушки в качестве параметра hmod следует передавать нулевое значение.
Если же предполагается слежение за другими приложениями (за потоками других процессов), то функция ловушки должна быть экспортируемой функцией DLL. Тогда в функцию SetWindowsHookEx передается дескриптор модуля DLL (похоже, что это адрес в адресном пространстве процесса, куда спроецирован файл DLL). Библиотека (DLL) может загружаться как при запуске приложения (если используется так называемое load-time связывание), так и динамически при помощи API-функции LoadLibrary:
function LoadLibrary(lpLibFileName: PChar): HMODULE; stdcall;
Функция принимает в качестве параметра путь DLL и возвращает дескриптор загруженного модуля (или 0 в случае ошибки). Если библиотека больше не нужна, то можно вызвать функцию FreeLibrary, передав в качестве единственного параметра возращенный ранее функцией LoadLibrary дескриптор модуля DLL
Возвращаясь к теме расположения ловушки зададимся вопросом: почему именно DLL? Чем плохо расположение ловушки в ЕХЕ-модуле приложения? Самое время вспомнить о том, что каждый процесс в Windows выполняется в своем собственном адресном пространстве. Поэтому адрес функции в исполняемом файле одного процесса вполне может быть адресом структуры данных где-то внутри другого процесса (рис. 10.1).
Рис. 10.1. Пример адресного пространства разных процессов
В отличие от ЕХЕ-файлов, файлы библиотек легко проецируются в адресное пространство использующего их процесса. Разместив функцию ловушки в DLL и указав дескриптор модуля этой DLL, мы предоставляем системе полную информацию для того, чтобы она смогла:
• спроецировать библиотеку с ловушкой в адресное пространство исследуемого процесса;
• однозначно определить положение (адрес) функции-ловушки в адресном пространстве исследуемого процесса.
Описанные выше манипуляции с DLL проиллюстрированы на рис. 10.2 (Процесс 2 на рисунке – процесс, в который внедряется ловушка).
Рис. 10.2. Загрузка DLL с ловушкой в адресное пространство исследуемого процессаТеперь нет никаких препятствий в вызове функции-ловушки при наблюдении за другим процессом.
Примечание
В каждой библиотеке реализуется функция DIIMain, вызываемая (упрощенно) при загрузке/выгрузке библиотеки. Жаль только, что при описанном способе подгрузки DLL эта функция не вызывается. Это приводит к усложнению кодаловушки, в чем вы сможете вскоре убедиться.
10.2. Программа «Оконный шпион»
Перейдем, наконец, к практической части главы: рассмотрим создание программы, позволяющей составлять список (а точнее, дерево) всех окон, просматривать и изменять их свойства, а также осуществлять перехват сообщений выбранного окна (недаром мы столько времени потратили на рассмотрение ловушек Windows).
Несмотря на свое «противозаконное» название, рассматриваемая программа может весьма пригодиться при отладке приложений, а отнюдь непри их взломе и шпионаже (хотя многое зависит от добросовестности лица, использующего программу). В частности, с помощью этой программы была найдена ошибка, на долгое время закравшаяся в один из примеров гл. 2: из-за неправильной установки стилей при ручном создании главного окна программы не удавалось добиться правильной перерисовки элемента управления «рамка».
Составление списка открытых окон
Список (а точнее, дерево) окон, открытых в момент запуска программы, показан на рис. 10.3.
Рис. 10.3. Дерево открытых окон
Форма, показанная на рис. 10.3, имеет имя f rmMain. Элемент управления TreeView имеет имя tree. Часть программы, отвечающая за построение дерева, относительно проста. Она использует вскользь рассмотренный в гл.2 механизм перечисления окон. Составление дерева окон начинается с процедуры LoadWindowsTree, которая и запускает перечисление окон верхнего уровня, то есть окон, родителем которых формально является окно Рабочего стола (листинг 10.1).
Листинг 10.1. Начало составления дерева окон
procedure TfrmMain.LoadWindowsTree();
var
desktop: TTreeNode;
//enInfo: TEnumInfo;
begin
tree.Items.Clear;
//Добавление узла для Рабочего стола
desktop := tree.Items.Add(tree.Items.GetFirstNode, \'Рабочий
стол\');
//Перечисление окон
enInfo.tree := tree;
enInfo.parent := desktop;
EnumWindows(Addr(NewWindow), Integer(Addr(enInfo)));
end;
Сразу следует привести объявление структуры, интенсивно используемой (далее это будет видно) при составлении дерева:
Type
TEnumInfo = Record
tree: TTreeView; //Компонент TreeView
parent: TTreeNode; //Элемент дерева, соответствующий
//текущему окну, дочерние
//окна которого перечисляются
end;
При нахождении каждого нового окна вызывается функция NewWindow (ее адрес передан в API-функцию EnumWindows). Функция NewWindow (листинг 10.2) решает две задачи. Во-первых, она добавляет в дерево элемент, соответствующий найденному окну. Во-вторых, запускает поиск дочерних окон относительно найденного окна, что позволяет перечислить все окна (от главной формы приложения до кнопок, надписей и т. д.).
Листинг 10.2.
Добавление в дерево информации об окне и поиск дочерних окон
function NewWindow(wnd: HWND; param: LPARAM):BOOL; stdcall;
var
wndNode, parentNode: TTreeNode;
begin
wndNode := AddWindowToTree(wnd); //Добавление информации об
окне в дерево
//Перечисление дочерних окон
parentNode := enInfo.parent;
enInfo.parent := wndNode;
EnumChildWindows(wnd, Addr(NewWindow), param);
enInfo.parent := parentNode;
//Продолжать перечисление (после перечисления
//всех дочерних окон)
NewWindow := True;
end;
Используемая в листинге 10.3 функция AddWindowToTree добавляет элемент, соответствующий найденному окну, в дерево (определяет текст заголовка окна и имя оконного класса):
Листинг 10.3.
Добавление элемента, соответствующего окну, в дерево
function AddWindowToTree(wnd: HWND): TTreeNode;
var
caption, classname: String;