Что мне нужно сделать, чтобы заставить процедуру WH_SHELL или WH_CBT принимать события из других процессов?
Я пытаюсь использовать SetWindowsHookEx
для создания крюка WH_SHELL
для получения уведомлений об общесистемных событиях HSHELL_WINDOWCREATED
и HSHELL_WINDOWDESTROYED
. Я передаю 0 для окончательного аргумента dwThreadId
, который, согласно docs, должен "связывать процедуру hook со всеми существующими потоками, запущенными в том же рабочий стол как вызывающий поток". Я также передаю дескриптор моей DLL (HInstance
в Delphi) для параметра hMod
, как и все примеры, на которые я смотрел.
Тем не менее, я только когда-либо получаю уведомление о окнах, созданных моим собственным приложением, и, чаще всего, мои тесты приводят к тому, что процесс рабочего стола падает в пламени, когда я закрываю свое приложение. Прежде чем спросить, я звоню UnhookWindowsHookEx
. Я также всегда вызываю CallNextHookEx
из моего обработчика.
Я запускаю тестовое приложение из ограниченной учетной записи пользователя, но до сих пор я не нашел никаких намеков, указывающих, что это будет играть роль... (хотя это меня действительно удивляет)
AFAICT, я сделал все по книге (очевидно, я этого не делал, но до сих пор я не вижу, где).
Я использую Delphi (2007), но это не имеет большого значения, я думаю.
РЕДАКТИРОВАТЬ: Возможно, я должен был упомянуть об этом раньше: я загрузил и попробовал несколько примеров (хотя, к сожалению, для Delphi не так много доступных, особенно для WH_SHELL
или WH_CBT
). Хотя они не разбивают систему, как это делает мое тестовое приложение, они по-прежнему не захватывают события из других процессов (хотя я могу проверить с помощью ProcessExplorer, что они загружаются в них в порядке). Таким образом, кажется, что что-то не так с моей конфигурацией системы или примеры неправильны или просто невозможно захватить события из других процессов. Может ли кто-нибудь просветить меня?
EDIT2: ОК, здесь источник моего тестового проекта.
DLL, содержащая процедуру hook:
library HookHelper;
uses
Windows;
{$R *.res}
type
THookCallback = procedure(ACode, AWParam, ALParam: Integer); stdcall;
var
WndHookCallback: THookCallback;
Hook: HHook;
function HookProc(ACode, AWParam, ALParam: Integer): Integer; stdcall;
begin
Result := CallNextHookEx(Hook, ACode, AWParam, ALParam);
if ACode < 0 then Exit;
try
if Assigned(WndHookCallback)
// and (ACode in [HSHELL_WINDOWCREATED, HSHELL_WINDOWDESTROYED]) then
and (ACode in [HCBT_CREATEWND, HCBT_DESTROYWND]) then
WndHookCallback(ACode, AWParam, ALParam);
except
// plop!
end;
end;
procedure InitHook(ACallback: THookCallback); register;
begin
// Hook := SetWindowsHookEx(WH_SHELL, @HookProc, HInstance, 0);
Hook := SetWindowsHookEx(WH_CBT, @HookProc, HInstance, 0);
if Hook = 0 then
begin
// ShowMessage(SysErrorMessage(GetLastError));
end
else
begin
WndHookCallback := ACallback;
end;
end;
procedure UninitHook; register;
begin
if Hook <> 0 then
UnhookWindowsHookEx(Hook);
WndHookCallback := nil;
end;
exports
InitHook,
UninitHook;
begin
end.
И основная форма приложения с помощью hook:
unit MainFo;
interface
uses
Windows, SysUtils, Forms, Dialogs, Classes, Controls, Buttons, StdCtrls;
type
THookTest_Fo = class(TForm)
Hook_Btn: TSpeedButton;
Output_Lbx: TListBox;
Test_Btn: TButton;
procedure Hook_BtnClick(Sender: TObject);
procedure Test_BtnClick(Sender: TObject);
public
destructor Destroy; override;
end;
var
HookTest_Fo: THookTest_Fo;
implementation
{$R *.dfm}
type
THookCallback = procedure(ACode, AWParam, ALParam: Integer); stdcall;
procedure InitHook(const ACallback: THookCallback); register; external 'HookHelper.dll';
procedure UninitHook; register; external 'HookHelper.dll';
procedure HookCallback(ACode, AWParam, ALParam: Integer); stdcall;
begin
if Assigned(HookTest_Fo) then
case ACode of
// HSHELL_WINDOWCREATED:
HCBT_CREATEWND:
HookTest_Fo.Output_Lbx.Items.Add('created handle #' + IntToStr(AWParam));
// HSHELL_WINDOWDESTROYED:
HCBT_DESTROYWND:
HookTest_Fo.Output_Lbx.Items.Add('destroyed handle #' + IntToStr(AWParam));
else
HookTest_Fo.Output_Lbx.Items.Add(Format('code: %d, WParam: $%x, LParam: $%x', [ACode, AWParam, ALParam]));
end;
end;
procedure THookTest_Fo.Test_BtnClick(Sender: TObject);
begin
ShowMessage('Boo!');
end;
destructor THookTest_Fo.Destroy;
begin
UninitHook; // just to make sure
inherited;
end;
procedure THookTest_Fo.Hook_BtnClick(Sender: TObject);
begin
if Hook_Btn.Down then
InitHook(HookCallback)
else
UninitHook;
end;
end.
Ответы
Ответ 1
Проблема заключается в том, что ваша DLL файл с крючками фактически загружается в несколько разных адресных пространств. В любое время, когда Windows обнаруживает событие в каком-либо внешнем процессе, которое должно обрабатываться вашим крюком, он загружает DLL-крюк в этот процесс (если он еще не загружен, конечно).
Однако каждый процесс имеет собственное адресное пространство. Это означает, что указатель функции обратного вызова, который вы передали в InitHook(), имеет смысл только в контексте вашего EXE (почему он работает для событий в вашем приложении). В любом другом процессе указатель мусор; он может указывать на недопустимое расположение памяти или (что еще хуже) на некоторый случайный раздел кода. Результатом может быть либо нарушение прав доступа, либо повреждение памяти.
Как правило, решение заключается в использовании своего рода межпроцессного общения (IPC) для правильного уведомления вашего EXE. Самым безболезненным способом для вашего случая было бы опубликовать сообщение и вставить необходимую информацию (событие и HWND) в ее WPARAM/LPARAM. Вы можете использовать WM_APP + n или создать его с помощью RegisterWindowMessage(). Убедитесь, что сообщение опубликовано и не отправлено, чтобы избежать каких-либо взаимоблокировок.
Ответ 2
Это может быть третичным по отношению к вашему вопросу, но, как вы видите, перехватчики очень трудны для правильного выбора - если вы можете избежать этого любым способом, сделайте это. Вы столкнетесь со всеми проблемами, особенно с Vista, где вам придется иметь дело с UIPI.
Ответ 3
Просто чтобы прояснить то, что "efotinis" упомянул о отправке сообщений обратно в ваш процесс - wParam и lParam, которые вы публикуете в своем основном процессе, не могут быть указателями, они могут быть просто "числами".
Например, скажем, вы подключаете сообщение WM_WINDOWPOSCHANGING, окна передают вам указатель на WINDOWPOS в lparam. Вы не можете просто отправить этот lparam в свой основной процесс, потому что память, на которую указывает lparam, действительна только в процессе получения сообщения.
Это то, что означало "эфотинис", когда он сказал "втиснул необходимую информацию (событие и HWND) в свой WPARAM/LPARAM". Если вы хотите передать более сложные сообщения обратно, вам понадобится использовать некоторые другие IPC (например, именованные каналы, TCP или файлы с отображением памяти).
Ответ 4
Lol, похоже, что ошибка находится в тестовом коде.
Если вы создаете две отдельные кнопки, один для Init и один для UnInit (я предпочитаю Exit).
procedure THooktest_FO.UnInitClick(Sender: TObject);
begin
UninitHook;
end;
procedure THooktest_FO.InitClick(Sender: TObject);
begin
InitHook(HookCallback)
end;
Запустите приложение. Нажмите "Инициировать", а затем кнопку "Тест", появится следующий вывод:
created handle #1902442
destroyed handle #1902442
created handle #1967978
created handle #7276488
Затем появится окно сообщения.
Если вы нажмете ok, вы получите:
destroyed handle #1967978
НТН
Ответ 5
Я нашел базовую документацию Delphi для SetWindowsHookEx. Но текст немного расплывчатый.
function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc;
hmod: HInst; dwThreadId: DWORD): HHOOK;
-
hmod: дескриптор модуля (DLL), содержащий функцию hook, на которую указывает параметр lpfn. Этот параметр должен быть установлен на ноль, если dwThreadId идентифицирует поток, созданный текущим процессом, а dlpfn указывает на функцию hook, расположенную в коде, связанном с текущим процессом.
-
dwThreadId: Идентификатор потока, к которому будет привязана установленная функция hook. Если этот параметр установлен на ноль, крючок будет общесистемным, связанным со всеми существующими потоками.
Кстати, для параметра hmod вы должны использовать дескриптор модуля. (HINSTANCE указывает на дескриптор приложения).
hand := GetModuleHandle('hookhelper.dll');
Hook := SetWindowsHookEx(WH_SHELL, @HookProc, hand, 0);
Но хотя рука отличается от HINSTANCE, она по-прежнему показывает тот же результат.