我需要做什么才能使 WH_SHELL 或 WH_CBT 挂钩过程接收来自其他进程的事件?

StackOverflow https://stackoverflow.com/questions/299370

  •  08-07-2019
  •  | 
  •  

我正在尝试使用 SetWindowsHookEx 设立一个 WH_SHELL 挂钩以获取系统范围的通知 HSHELL_WINDOWCREATEDHSHELL_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 不同,但它仍然显示相同的结果。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top