【问题标题】:What do I have to do to make my WH_SHELL or WH_CBT hook procedure receive events from other processes?我必须做什么才能让我的 WH_SHELL 或 WH_CBT 挂钩程序接收来自其他进程的事件?
【发布时间】:2010-09-22 21:33:15
【问题描述】:

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

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

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

AFAICT,我按部就班地做了一切(显然我没有,但到目前为止我看不到在哪里)。

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

编辑:也许我应该在之前提到过:我确实下载并尝试了几个示例(但不幸的是,Delphi 没有那么多可用的示例 - 特别是 WH_SHELL 或 @987654332 没有@)。虽然它们不会像我的测试应用程序那样使系统崩溃,但它们仍然不会捕获来自其他进程的事件(即使我可以使用 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.

以及使用钩子的app的主要形式:

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.

【问题讨论】:

    标签: windows delphi winapi hook


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

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

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

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

        【讨论】:

        • 谢谢,这听起来很有希望。我会在星期一试试。
        • 是的,现在完美运行。再次感谢您很好地解释了为什么我的方法不起作用。
        • @Oliver 您能否进一步说明您选择的通信方式?
        • @NGLN :我选择了基于消息的方法。我现在将消息接收器的句柄传递给InitHook,而不是指向回调的指针。
        【解决方案4】:

        我找到了 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 不同,但仍显示相同的结果。

        【讨论】:

        • 我直接从样本中获取了 HInstance(例如在 About.com 上)。它并不总是指向应用程序。在 DLL 中,它指向 DLL 本身。这就是为什么您可以在 TBitmap.LoadFromResourceName 等中使用它的原因。从 DLL 内部。
        • 我检查了值,但 DLL 中的 HInstance 和 App 中的 HInstance 是相同的。但这仍然无济于事,所以我们需要更多信息;-)(幸运的是我喜欢这样的谜题)
        【解决方案5】:

        大声笑,错误似乎在测试代码中。

        如果您创建两个单独的按钮,一个用于 Init,一个用于 UnInit(我更喜欢 Exit)。

        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

        【讨论】:

        • 是的,我得到了相同的输出。该按钮是一个 AllowAllUp = True 的 TSpeedButton。我的问题是我没有收到有关其他进程的任何通知,即当我打开或关闭其他应用程序时。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-08-04
        • 1970-01-01
        • 1970-01-01
        • 2022-01-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多