我试图使用SetWindowsHookEx
来build立一个WH_SHELL
挂钩来获得系统范围的HSHELL_WINDOWCREATED
和HSHELL_WINDOWDESTROYED
事件的通知。 我把0传递给最后一个dwThreadId
参数,根据文档 , 这个参数应该“将钩子过程与调用线程在同一桌面上运行的所有现有线程相关联”。 我也传递了我的DLL( HInstance
在Delphi中)的句柄hMod
参数,我所看到的所有例子。
然而,我只能得到我自己的应用程序创build的窗口的通知,而且往往会导致桌面进程一旦closures我的应用程序就会熄灭。 在你问之前,我打电话给UnhookWindowsHookEx
。 我也总是从我的处理程序中调用CallNextHookEx
。
我从一个有限的用户帐户运行我的testing应用程序,但到目前为止,我还没有发现任何暗示,这将发挥作用…(虽然这实际上令我惊讶)
AFAICT,我做了所有的书(显然我没有,但到目前为止,我不知道在哪里)。
我正在使用delphi(2007年),但这应该不是真的,我想。
编辑:也许我应该提到这之前:我下载并尝试了几个例子(虽然不幸的是没有那么多的delphi – 特别是没有WH_SHELL
或WH_CBT
)。 虽然他们不会像我的testing应用程序那样崩溃系统,但是他们仍然不会捕获来自其他进程的事件(尽pipe我可以使用ProcessExplorervalidation它们是否可以加载到其中)。 所以看起来我的系统configuration有问题,或者这个例子错了,或者根本无法从其他进程捕获事件。 任何人都可以启发我吗?
编辑2:好的,这是我的testing项目的来源。
包含挂钩过程的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.
而使用钩子的应用程序的主要forms:
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()创建一个。 确保邮件已发布,而不是发送,以避免任何死锁。
这可能是你的问题的第三大问题,但是正如你所看到的,钩子很难得到正确的结果 – 如果你可以避免使用这种方法,那就去做吧。 你会遇到各种各样的问题,特别是在Vista上,你将不得不面对UIPI。
只是为了澄清“efotinis”中提到的有关将消息发布回您的进程的内容 – 您发布到主进程的wParam和lParam不能是指针,它们只能是“数字”。
例如,让我们说你挂钩了WM_WINDOWPOSCHANGING消息,windows向你传递一个指向lparam中的WINDOWPOS的指针。 你不能把这个lparam放回你的主进程,因为lparam所指向的内存只在收到该消息的进程中有效。
当他说“将所需的信息(事件和HWND)塞入其WPARAM / LPARAM”时,这就是“efotinis”的意思。 如果你想传递更复杂的消息,你需要使用一些其他的IPC(如命名管道,TCP或内存映射文件)。
大声笑,它看起来像错误是在测试代码。
如果你创建两个单独的按钮,一个用于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
然后显示消息框。
如果你点击确定你会得到:
destroyed handle #1967978
HTH
我找到了SetWindowsHookEx的Delphi基础文档。 但文字有点模糊。
function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc; hmod: HInst; dwThreadId: DWORD): HHOOK;
hmod:包含由lpfn参数指向的钩子函数的模块(DLL)的句柄。 如果dwThreadId标识由当前进程创建的线程,则该参数必须设置为零。dlpfn指向位于与当前进程关联的代码中的钩子函数。
dwThreadId:安装的钩子函数将被关联到的线程的标识符。 如果此参数设置为零,则挂钩将是与所有现有线程关联的全系统挂钩。
顺便说一句,对于hmod参数,你应该使用模块句柄。 (HINSTANCE指向应用程序句柄)。
hand := GetmoduleeHandle('hookhelper.dll'); Hook := SetWindowsHookEx(WH_SHELL, @HookProc, hand, 0);
但是,虽然手与HINSTANCE不同,但仍显示相同的结果。