我需要做什么才能使 WH_SHELL 或 WH_CBT 挂钩过程接收来自其他进程的事件?
题
我正在尝试使用 SetWindowsHookEx
设立一个 WH_SHELL
挂钩以获取系统范围的通知 HSHELL_WINDOWCREATED
和 HSHELL_WINDOWDESTROYED
事件。决赛我通过了0分 dwThreadId
论证,根据 文档, ,应该“将挂钩过程与与调用线程在同一桌面中运行的所有现有线程相关联”。我还将句柄传递给我的 DLL (HInstance
在德尔福) hMod
参数就像我看过的所有例子一样。
然而,我只收到由我自己的应用程序创建的窗口的通知,而且通常情况下,一旦我关闭我的应用程序,我的测试就会导致桌面进程陷入困境。在你问之前,我先打电话 UnhookWindowsHookEx
. 。我也经常打电话 CallNextHookEx
从我的处理程序内部。
我正在从有限的用户帐户运行我的测试应用程序,但到目前为止我还没有发现任何提示表明这会发挥作用......(虽然这确实让我惊讶)
AFAICT,我按照书本做了一切(显然我没有,但到目前为止我不知道在哪里)。
我正在使用 Delphi (2007),但我认为这并不重要。
编辑: 也许我应该之前提到这一点:我确实下载并尝试了几个示例(尽管不幸的是没有那么多可用于 Delphi 的示例 - 尤其是没有 WH_SHELL
或者 WH_CBT
)。虽然它们不会像我的测试应用程序那样使系统崩溃,但它们仍然不会捕获来自其他进程的事件(即使我可以使用 ProcessExplorer 验证它们是否正确加载到其中)。因此,看来我的系统配置有问题,或者示例是错误的,或者根本不可能从其他进程捕获事件。谁能启发我吗?
编辑2: 好的,这是我的测试项目的来源。
包含钩子过程的DLL:
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.
以及使用钩子的应用程序的主要形式:
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.
解决方案
问题是你的钩子DLL实际上被加载到几个不同的地址空间。任何时候Windows检测到一些必须由钩子处理的外部进程中的事件,它会将钩子DLL加载到该进程中(当然,如果它尚未加载)。
但是,每个进程都有自己的地址空间。这意味着您在InitHook()中传递的回调函数指针仅在EXE的上下文中有意义(这就是它适用于您的应用程序中的事件的原因)。在任何其他指针垃圾的过程中;它可能指向无效的内存位置或(更糟)进入某个随机代码段。结果可能是访问冲突或静默内存损坏。
通常,解决方案是使用某种进程间沟通(IPC)以正确通知您的EXE。对您的案例来说,最无痛的方法是发布消息并将所需信息(事件和HWND)塞入其WPARAM / LPARAM中。您可以使用WM_APP + n或使用RegisterWindowMessage()创建一个。确保邮件已发布但未发送,以避免任何死锁。
其他提示
这可能是您的问题的第三方,但正如您所看到的,钩子非常难以正确 - 如果您可以通过任何方式避免使用它,请执行此操作。你会遇到各种各样的问题,特别是在你需要处理UIPI的Vista上。
只是为了澄清<!>“efotinis <!>”的内容。提到将消息发送回你的进程 - 你发布到主进程的wParam和lParam不能是指针,它们只能是<!> quot; numbers <!> quot;。
例如,假设您挂钩了WM_WINDOWPOSCHANGING消息,Windows会向您传递指向lparam中WINDOWPOS的指针。您不能将该lparam发布回主进程,因为lparam指向的内存仅在收到消息的进程中有效。
这就是<!>“efotinis <!>”;当他说<!>时将所需信息(事件和HWND)塞入其WPARAM / LPARAM <!>中;如果你想传递更复杂的消息,你需要使用其他一些IPC(比如命名管道,TCP或内存映射文件)。
大声笑,看起来错误出现在测试代码中。
如果你创建两个单独的按钮,一个用于Init,一个用于UnInit(我更喜欢退出)。
procedure THooktest_FO.UnInitClick(Sender: TObject);
begin
UninitHook;
end;
procedure THooktest_FO.InitClick(Sender: TObject);
begin
InitHook(HookCallback)
end;
启动应用。单击Init,然后单击Test按钮,显示以下输出:
created handle #1902442
destroyed handle #1902442
created handle #1967978
created handle #7276488
然后会显示消息框。
如果单击“确定”,则会获得:
destroyed handle #1967978
HTH
我找到了 SetWindowsHookEx 的 Delphi 基础文档。但文字有点模糊。
function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc;
hmod: HInst; dwThreadId: DWORD): HHOOK;
模块:包含 lpfn 参数指向的钩子函数的模块(DLL)的句柄。如果 dwThreadId 标识当前进程创建的线程,则该参数必须设置为零,并且 dlpfn 指向位于与当前进程关联的代码中的钩子函数。
dwThreadId:已安装的钩子函数将关联到的线程的标识符。如果此参数设置为零,则挂钩将是与所有现有线程关联的系统范围挂钩。
顺便说一句,对于 hmod 参数,您应该使用模块句柄。(HINSTANCE 指向应用程序句柄)。
hand := GetModuleHandle('hookhelper.dll');
Hook := SetWindowsHookEx(WH_SHELL, @HookProc, hand, 0);
尽管 hand 与 HINSTANCE 不同,但它仍然显示相同的结果。