Аннотация :
Статья посвящена реализации глобальных хуков. В ней рассматриваются типичные проблемы, связанные
с различием виртуальных адресов перехватывающей DLL в адресных пространствах различных процессов, описывается
способ создания разделяемого сегмента данных, приводится пример DLL, реализующей глобальный хук.
Иногда бывает полезен следующий прием:
хук устанавливается на обработку единичного
сообщения после обработки которого сразу же
снимается. Заметим, что в этом случае нет нужды в обработке цепочки хуков, т.е. CallNextHookEx.
Вот пример (Дельфи) для получения имени исполнимого файла по хендлу окна.
procedure TForm1.Button1Click(Sender: TObject);
var
ahwnd: THandle;
begin
{Ищем по заголовку}
ahwnd := FindWindow(nil, PChar(Edit1.Text));
if ahwnd <> 0 then
QryName(GetWindowThreadProcessID(ahwnd, nil), Handle);
end;
--- lib0.dpr ---
library lib0;
uses
Messages,
Windows;
function Answer( nCode: Integer; wprm: WParam; lprm: LParam): LResult;
stdcall;
type
PMsg = ^TMsg;
var
buffer : array [0..MAX_PATH] of Char;
cd : TCopyDataStruct;
msg : PMsg;
Caller : HWND;
AHook: HHook;
begin
Result := 0;
msg := PMsg(lprm);
if (msg.Message = 0) and (msg.LParam <> 0) then
begin
AHook := msg.lParam;
Caller := msg.wParam;
cd.cbData := GetModuleFileName(0, buffer, SizeOf(buffer))+1;
cd.lpData := @buffer;
cd.dwData := GetCurrentThreadID;
SendMessage(Caller, WM_COPYDATA, 0, LParam(@cd));
UnHookWindowsHookEx(AHook);
PostThreadMessage(GetCurrentThreadID, 0, 0, 0);
end;
end;
procedure QryName(tid: DWord; Caller: HWND);
var
AHook : Hhook;
begin
AHook := SetWindowsHookEx(WH_GETMESSAGE, Answer, Hinstance, tid);
if AHook <> 0 then
PostThreadMessage(tid, 0, Caller, AHook);
end;
После UnhookWindowsHookEx загруженная в чужой процесс
на WH_GETMESSAGE библиотека останется там даже если ее счетчик ее загрузок обнулится.
Реально она будет выгружаться только после обработки
очередного сообщения, посланного Post*Message.
Т.е., чтобы гарантировать выгрузку, после Unhook
следует послать потоку любое сообщение (обычно это WM_NULL).
Аналогичным поведением обладают и многие другие типы хуков.
При использовании WH_GETMESSAGE, как и некоторых
других типов хуков, можно передать хуковой процедуре
пару параметров, например,hook и хендл окна, получающего ответные сообщения.
Для WH_GETMESSAGE это wParam, lParam в отправляемом
для установки хука сообщении.
Для многих случаев это позволяет обойтись без
разделяемых секций (создавать которые, например, с
помощью Дельфи затруднительно) или memory mapped file.
Одна большая проблема состоит в том, что очень часто при юзаньи хуков приходится использовать ресурсы(менюшки, диалоги, etc.), а ведь наша DLL находится в ЧУЖОМ адресном пространстве !
И при использовании GetModuleHandle мы НЕ ПОЛУЧИМ дескриптор DLL !!!
Это будет дескриптор ЧУЖОГО процесса... :)
И LoadMenu(GetModuleHandle(NULL), IMD_DLLMENU, ......) даст нам (если даст) ЧУЖОЕ меню !!!
Поэтому единственный путь здесь — это отлавливать в DllMain момент загрузки DLL и сохранять значение DLLhinst...
Отличная статья.
Хочется добавить что через memory mapped file поддержать единые данные тоже можно.
По поводу коммуникации в рамках одного ДЛЛ, но в разных адресных пространствах. Если стоят хуки на сообщения и они завязаны на определённые threads можно между хуками (threadами соответственно) перебрасываться registred messages. После получения и перехвата оных в любом thread по желанию совершать operations.
Not tested yet, but seems to be working.
Если ресурсы в библиотеке, проблем нет:
ее hinstance можно узнать в dllmain,
а, например, в Дельфи есть такая глобальная
переменная.
Ну, и при желании можно получить свой базовый
адрес из адреса любой экспортируемой функции.
static LRESULT CALLBACK msghook(int nCode, WPARAM wParam, LPARAM lParam)
{
// If the value of nCode is < 0, just pass it on and return 0
// this is required by the specification of hook handlers
// Если значение nCode < 0, просто передаем его дальше и возвращаем 0
// этого требует спецификация обработчиков хуковif(nCode < 0)
{ /* передаем дальше */
CallNextHookEx(hook, nCode,
wParam, lParam);
return 0;
} /* передаем дальше */
// <...>
// Передаем сообщение следующему хукуreturn CallNextHookEx(hook, nCode,
wParam, lParam);
} // msghook
В соответствии со документацией
для CallWndProc, CallWndRetProc, ForegroundIdleProc, GetMsgProc:
If nCode is less than zero, the hook procedure must return the value returned by CallNextHookEx.
If nCode is less than zero, the hook procedure must pass the message to the CallNextHookEx function without further processing and should return the value returned by CallNextHookEx.
LП>Авторы : LП>Lexey (перевод)
LП>Аннотация : LП>Статья посвящена реализации глобальных хуков. В ней рассматриваются типичные проблемы, связанные LП>с различием виртуальных адресов перехватывающей DLL в адресных пространствах различных процессов, описывается LП>способ создания разделяемого сегмента данных, приводится пример DLL, реализующей глобальный хук.
А подскажите, пожалуйста, есть ли возможность повесить хук на приложение запущенное под другим пользователем?
>приводится пример DLL, реализующей глобальный хук
а что это переменная HHOOK hook; не зашарена ?
как же он передает ее значение в DllMain/clearMyHook/UnhookWindowsHookEx и msghook/CallNextHookEx
эти вызовы же выполняются в иных адресных пространствах
Только на РСДН помимо ответа на вопрос, можно получить еще список орфографических ошибок и узнать что-то новое из грамматики английского языка (c) http://www.rsdn.ru/forum/cpp/4720035.1.aspx
>>приводится пример DLL, реализующей глобальный хук
S>а что это переменная HHOOK hook; не зашарена ? S>как же он передает ее значение в DllMain/clearMyHook/UnhookWindowsHookEx и msghook/CallNextHookEx S>эти вызовы же выполняются в иных адресных пространствах
Здравствуйте, Andrew S, Вы писали:
>>>приводится пример DLL, реализующей глобальный хук
AS>Я про это уже писал, народ никак не реагирует .
А он уже сам давно накололся и запомнил, что так низзя Кстати у Рихтера, насколько я помню, тоже этот недочет есть.
>>>>приводится пример DLL, реализующей глобальный хук
AS>>Я про это уже писал, народ никак не реагирует . C>А он уже сам давно накололся и запомнил, что так низзя Кстати у Рихтера, насколько я помню, тоже этот недочет есть.
Имеется в виду команда rsdn. У Рихтера — все нормально.