12

我正在尝试使用SetWindowsHookEx设置WH_SHELL挂钩来获取系统范围HSHELL_WINDOWCREATEDHSHELL_WINDOWDESTROYED事件的通知。我为最后一个dwThreadId参数传递了 0,根据文档,它应该“将钩子过程与在与调用线程相同的桌面上运行的所有现有线程相关联”。我还将参数传递给我的 DLL(HInstance在 Delphi 中)的句柄,hMod就像我查看的所有示例一样。

然而,我只收到由我自己的应用程序创建的窗口的通知,而且 - 通常情况下 - 我的测试导致桌面进程在我关闭我的应用程序后陷入困境。在你问之前,我会打电话UnhookWindowsHookEx。我也总是CallNextHookEx从我的处理程序中调用。

我正在从一个有限的用户帐户运行我的测试应用程序,但到目前为止,我还没有发现任何暗示这会起作用的提示......(虽然这实际上让我感到惊讶)

AFAICT,我按照书本做了所有事情(显然我没有,但到目前为止我看不到在哪里)。

我正在使用 Delphi (2007),但我认为这并不重要。

编辑:也许我之前应该提到这一点:我确实下载并尝试了几个示例(尽管不幸的是,Delphi 没有那么多可用的示例 - 特别是WH_SHELLor没有WH_CBT)。虽然它们不会像我的测试应用程序那样使系统崩溃,但它们仍然不会捕获来自其他进程的事件(即使我可以使用 ProcessExplorer 验证它们是否可以正常加载到它们中)。因此,我的系统配置似乎有问题,或者示例错误,或者根本无法从其他进程捕获事件。任何人都可以启发我吗?

EDIT2:好的,这是我的测试项目的来源。

包含挂钩过程的 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.
4

5 回答 5

10

问题是您的钩子 DLL 实际上被加载到几个不同的地址空间中。每当 Windows 在某个外部进程中检测到必须由您的钩子处理的事件时,它会将钩子 DLL 加载到该进程中(当然,如果它尚未加载)。

但是,每个进程都有自己的地址空间。这意味着您在 InitHook() 中传递的回调函数指针仅在您的 EXE 上下文中才有意义(这就是它适用于您应用程序中的事件的原因)。在任何其他进程中,该指针都是垃圾;它可能指向无效的内存位置,或者(更糟)指向一些随机代码部分。结果可能是访问冲突或静默内存损坏。

通常,解决方案是使用某种进程间通信(IPC) 来正确通知您的 EXE。对于您的案例,最轻松的方法是发布一条消息并将所需的信息(事件和 HWND)塞入其 WPARAM/LPARAM。您可以使用 WM_APP+n 或使用 RegisterWindowMessage() 创建一个。确保消息已发布而不是发送,以避免任何死锁。

于 2008-11-21T23:11:02.353 回答
2

这可能是您的问题的第三个问题,但是正如您所看到的,钩子很难正确处理 - 如果您可以通过任何方式避免使用它,那就去做吧。你会遇到各种各样的问题,尤其是在你必须处理 UIPI 的 Vista 上。

于 2008-12-14T19:52:44.650 回答
2

只是为了澄清“efotinis”提到的关于将消息发布回您的进程的内容 - 您发布到主进程的 wParam 和 lParam 不能是指针,它们只能是“数字”。

例如,假设您挂钩 WM_WINDOWPOSCHANGING 消息,Windows 将向您传递一个指向 lparam 中的 WINDOWPOS 的指针。您不能只将该 lparam 发布回您的主进程,因为 lparam 指向的内存仅在接收消息的进程中有效。

当他说“将所需的信息(事件和 HWND)塞进其 WPARAM/LPARAM”时,这就是“efotinis”的意思。如果你想传回更复杂的消息,你将需要使用其他一些 IPC(如命名管道、TCP 或内存映射文件)。

于 2009-01-28T19:44:09.933 回答
0

大声笑,看起来错误在测试代码中。

如果您创建两个单独的按钮,一个用于 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

高温高压

于 2008-11-21T10:08:19.530 回答
0

我找到了 SetWindowsHookEx 的 Delphi 基础文档。但是文字有点模糊。

function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc; 
  hmod: HInst; dwThreadId: DWORD): HHOOK;
  • hmod:包含 lpfn 参数指向的钩子函数的模块(DLL)句柄。如果 dwThreadId 标识由当前进程创建的线程并且 dlpfn 指向位于与当前进程关联的代码中的挂钩函数,则必须将此参数设置为零。

  • dwThreadId:已安装的钩子函数将关联到的线程的标识符。如果此参数设置为零,则挂钩将是与所有现有线程相关联的系统范围挂钩。

顺便说一句,对于 hmod 参数,您应该使用模块句柄。(HINSTANCE 指向应用程序句柄)。

hand := GetModuleHandle('hookhelper.dll');
Hook := SetWindowsHookEx(WH_SHELL, @HookProc, hand, 0);

但是,尽管手与 HINSTANCE 不同,它仍然显示相同的结果。

于 2008-11-21T13:45:06.380 回答