我需要做什么才能使我的WH_SHELL或WH_CBT钩子程序从其他进程接收事件?

时间:2008-11-18 16:58:06

标签: windows delphi winapi hook

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

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

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

AFAICT,我按照这本书做了一切(显然我没有,但到目前为止,我没有看到哪里)。

我正在使用Delphi(2007),但我认为这不应该是真的重要。

编辑:也许我之前应该提到这个:我确实下载并尝试了几个例子(尽管遗憾的是Delphi没有那么多 - 特别是WH_SHELL没有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.

5 个答案:

答案 0 :(得分:9)

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

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

通常,解决方案是使用某种interprocess communication(IPC)来正确通知您的EXE。对您的案例来说,最无痛的方法是发布消息并将所需信息(事件和HWND)塞入其WPARAM / LPARAM中。您可以使用WM_APP + n或使用RegisterWindowMessage()创建一个。确保邮件已发布但未发送,以避免任何死锁。

答案 1 :(得分:2)

这可能是您的问题的第三方,但正如您所看到的,钩子非常很难做到 - 如果您可以通过任何方式避免使用它,请执行此操作。你会遇到各种各样的问题,特别是在你需要处理UIPI的Vista上。

答案 2 :(得分:2)

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

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

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

答案 3 :(得分:0)

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

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

答案 4 :(得分: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不同,但它仍然显示相同的结果。