windows 我该怎么做才能让我的 WH_SHELL 或 WH_CBT 钩子过程接收来自其他进程的事件?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/299370/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What do I have to do to make my WH_SHELL or WH_CBT hook procedure receive events from other processes?
提问by Oliver Giesen
I'm trying to use SetWindowsHookExto set up a WH_SHELLhook to get notified of system-wide HSHELL_WINDOWCREATEDand HSHELL_WINDOWDESTROYEDevents. I pass 0 for the final dwThreadIdargument which, according to the docs, should "associate the hook procedure with all existing threads running in the same desktop as the calling thread". I also pass in the handle to my DLL (HInstancein Delphi) for the hModparameter as did all the examples I looked at.
我试图用来SetWindowsHookEx设置一个WH_SHELL钩子来获得系统范围HSHELL_WINDOWCREATED和HSHELL_WINDOWDESTROYED事件的通知。我为最后一个dwThreadId参数传递了 0 ,根据文档,它应该“将钩子过程与与调用线程在同一桌面上运行的所有现有线程相关联”。我还将参数的句柄传递给我的 DLL(HInstance在 Delphi 中),hMod就像我查看的所有示例一样。
Yet, I only ever get notified of windows created by my own app and - more often than not - my tests result in the desktop process going down in flames once I close down my app. Before you ask, I do call UnhookWindowsHookEx. I also always call CallNextHookExfrom within my handler.
然而,我只会收到由我自己的应用程序创建的窗口的通知,而且 - 通常情况下 - 一旦我关闭我的应用程序,我的测试会导致桌面进程陷入困境。在你问之前,我会打电话给UnhookWindowsHookEx。我也总是CallNextHookEx从我的处理程序内部调用。
I am running my test app from a limited user account but so far I haven't found any hints indicating that this would play a role... (though that actually surprises me)
我正在从有限的用户帐户运行我的测试应用程序,但到目前为止我还没有发现任何提示表明这会起作用......(尽管这实际上让我感到惊讶)
AFAICT, I did everything by the book (obviously I didn't but so far I fail to see where).
AFAICT,我按照书做了所有事情(显然我没有,但到目前为止我没看到在哪里)。
I'm using Delphi (2007) but that shouldn't really matter I think.
我正在使用 Delphi (2007),但我认为这并不重要。
EDIT:Maybe I should have mentioned this before: I did download and try a couple of examples (though there are unfortunately not that many available for Delphi - especially none for WH_SHELLor WH_CBT). While they do not crash the system like my test app does, they still do not capture events from other processes (even though I can verify with ProcessExplorer that they get loaded into them alright). So it seems there is either something wrong with my system configuration or the examples are wrong or it is simply not possible to capture events from other processes. Can anyone enlighten me?
编辑:也许我之前应该提到这一点:我确实下载并尝试了几个示例(尽管遗憾的是 Delphi 没有那么多可用的 - 特别是没有用于WH_SHELL或WH_CBT)。虽然它们不会像我的测试应用程序那样使系统崩溃,但它们仍然不会从其他进程捕获事件(即使我可以使用 ProcessExplorer 验证它们是否正确加载到它们中)。因此,我的系统配置似乎有问题,或者示例有误,或者根本不可能从其他进程捕获事件。任何人都可以启发我吗?
EDIT2:OK, here's the source of my test project.
EDIT2:好的,这是我的测试项目的来源。
The DLL containing the hook procedure:
包含钩子过程的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.
And the main form of the app using the 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.
采纳答案by efotinis
The problem is that your hook DLL is actually being loaded into several different address spaces. Any time Windows detects an event in some foreign process that must be processed by your hook, it loads the hook DLL into that process (if it's not already loaded, of course).
问题是您的钩子 DLL 实际上被加载到几个不同的地址空间中。每当 Windows 在某个外部进程中检测到必须由您的钩子处理的事件时,它就会将钩子 DLL 加载到该进程中(当然,如果它还没有加载)。
However, each process has its own address space. This means that the callback function pointer that you passed in InitHook() only makes sense in the context of your EXE (that's why it works for events in your app). In any other process that pointer is garbage; it may point to an invalid memory location or (worse) into some random code section. The result can either be an access violation or silent memory corruption.
但是,每个进程都有自己的地址空间。这意味着您在 InitHook() 中传递的回调函数指针仅在您的 EXE 上下文中有意义(这就是它适用于您应用程序中的事件的原因)。在任何其他进程中,指针都是垃圾;它可能指向一个无效的内存位置,或者(更糟)指向某个随机代码部分。结果可能是访问冲突或静默内存损坏。
Generally, the solution is to use some sort of interprocess communication(IPC) to properly notify your EXE. The most painless way for your case would be to post a message and cram the needed info (event and HWND) into its WPARAM/LPARAM. You could either use a WM_APP+n or create one with RegisterWindowMessage(). Make sure the message is posted and not sent, to avoid any deadlocks.
通常,解决方案是使用某种进程间通信(IPC) 来正确通知您的 EXE。对于您的案例,最轻松的方法是发布消息并将所需信息(事件和 HWND)塞入其 WPARAM/LPARAM。您可以使用 WM_APP+n 或使用 RegisterWindowMessage() 创建一个。确保消息是发布而不是发送,以避免任何死锁。
回答by Ana Betts
This might be tertiary to your question, but as you're seeing, hooks are veryhard to get right - if you can avoid using this by any means, do it. You're going to run into all sorts of problems with them, especially on Vista where you'll have to deal with UIPI.
这可能是三级你的问题,但正如你所看到的,挂钩非常难以得到正确的-如果你能避免以任何方式利用这一点,做到这一点。你会遇到各种各样的问题,尤其是在你必须处理 UIPI 的 Vista 上。
回答by Maurice Flanagan
Just to clarify something that "efotinis" mentioned about posting messages back to your process - the wParam and lParam that you post to your main process can't be pointers, they can just be "numbers".
只是为了澄清“efotinis”提到的有关将消息发布回您的流程的内容 - 您发布到主流程的 wParam 和 lParam 不能是指针,它们只能是“数字”。
For example, lets say you hook the WM_WINDOWPOSCHANGING message, windows passes you a pointer to a WINDOWPOS in the lparam. You can't just post that lparam back to your main process because the memory the lparam is pointing to is only valid in the process that recieves the message.
例如,假设您钩住了 WM_WINDOWPOSCHANGING 消息,Windows 会向您传递一个指向 lparam 中的 WINDOWPOS 的指针。您不能只将该 lparam 发布回您的主进程,因为 lparam 指向的内存仅在接收消息的进程中有效。
This is what "efotinis" meant when he said " cram the needed info (event and HWND) into its WPARAM/LPARAM". If you want to pass more complex messages back your going to need to use some other IPC (like named pipes, TCP or memory mapped files).
这就是“efotinis”所说的“将所需信息(事件和 HWND)塞入其 WPARAM/LPARAM”的意思。如果您想将更复杂的消息传回,您将需要使用其他一些 IPC(如命名管道、TCP 或内存映射文件)。
回答by Toon Krijthe
Lol, it looks like the error is in the test code.
大声笑,看起来错误出在测试代码中。
If you create two separate buttons, one for Init and one for UnInit (I prefer Exit).
如果您创建两个单独的按钮,一个用于 Init,一个用于 UnInit(我更喜欢 Exit)。
procedure THooktest_FO.UnInitClick(Sender: TObject);
begin
UninitHook;
end;
procedure THooktest_FO.InitClick(Sender: TObject);
begin
InitHook(HookCallback)
end;
Start the app. Click Init and then The test button, the following output is shown:
启动应用程序。点击Init,然后点击test按钮,显示如下输出:
created handle #1902442
destroyed handle #1902442
created handle #1967978
created handle #7276488
Then the messagebox is shown.
然后显示消息框。
If you click ok you get:
如果你点击确定,你会得到:
destroyed handle #1967978
HTH
HTH
回答by Toon Krijthe
I found the Delphi base documentation for SetWindowsHookEx. But the text is a bit vague.
我找到了 SetWindowsHookEx 的 Delphi 基础文档。但是文字有点含糊。
function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc;
hmod: HInst; dwThreadId: DWORD): HHOOK;
hmod: A handle to the module (a DLL) containing the hook function pointed to by the lpfn parameter. This parameter must be set to zero if dwThreadId identifies a thread created by the current process an dlpfn points to a hook function located in the code associated with the current process.
dwThreadId: The identifier of the thread to which the installed hook function will be associated. If this parameter is set to zero, the hook will be a system-wide hook that is associated with all existing threads.
hmod:包含 lpfn 参数指向的钩子函数的模块(DLL)的句柄。如果 dwThreadId 标识由当前进程创建的线程,并且 dlpfn 指向位于与当前进程关联的代码中的钩子函数,则该参数必须设置为零。
dwThreadId:已安装的钩子函数将关联到的线程的标识符。如果此参数设置为零,则挂钩将是与所有现有线程关联的系统范围挂钩。
By the way, for the hmod parameter you should have used a module handle. (HINSTANCE points to the application handle).
顺便说一句,对于 hmod 参数,您应该使用模块句柄。(HINSTANCE 指向应用程序句柄)。
hand := GetModuleHandle('hookhelper.dll');
Hook := SetWindowsHookEx(WH_SHELL, @HookProc, hand, 0);
But although hand differs from HINSTANCE it still shows the same result.
但是,尽管 hand 与 HINSTANCE 不同,它仍然显示相同的结果。

