4

我正在一个 dll 中构建一个日志系统,其主要任务是捕获目标应用程序的一些特定 Windows 消息并执行一些任务。WndProc但不幸的是,方法 ( )只捕获了一条消息WM_GETMINMAXINFO。我错过了什么?为什么不是所有的 windows 消息都写入日志?

这是演示该问题的最小示例代码

动态链接库

library LogDll;

uses
  Winapi.Windows,
  Winapi.Messages,
  System.IOUtils,
  System.SysUtils,
  System.Classes;

{$R *.res}

type
  TLogInspector = class
  private
    WndHandle: THandle;
    ProcAddrInst: Pointer;
    OrgWndProc: Pointer;
  protected
    function CallOrgWndProc(Message: TMessage): LRESULT;
    procedure WndProc(var Message: TMessage); virtual;
  public
    constructor Create(AHandle: THandle); virtual;
  end;

var
  MainHook: HHook;
  Log : TLogInspector;

function TLogInspector.CallOrgWndProc(Message: TMessage): LRESULT;
begin
  Result := CallWindowProc(OrgWndProc, WndHandle, Message.Msg, Message.wParam,  Message.lParam);
end;

constructor TLogInspector.Create(AHandle: THandle);
begin
  OrgWndProc := Pointer(GetWindowLongPtr(AHandle, GWL_WNDPROC));
  ProcAddrInst := MakeObjectInstance(WndProc);
  WndHandle := AHandle;
  SetWindowLongPtr(WndHandle, GWL_WNDPROC, LONG_PTR(ProcAddrInst));
end;

procedure TLogInspector.WndProc(var Message: TMessage);
begin
  //log the current message
  TFile.AppendAllText('C:\Delphi\log.txt', 'WndProc '+IntToStr(Message.Msg)+sLineBreak);
  //call the org WndProc 
  Message.Result := CallOrgWndProc(Message);
end;


function HookCallBack(nCode: Integer;  _WPARAM: WPARAM; _LPARAM: LPARAM): LRESULT;  stdcall;
var
  lpClassName : array [0 .. 256] of Char;
begin
  if nCode = HCBT_CREATEWND then
    begin
      GetClassName(_WPARAM, lpClassName, 256);
      if lpClassName = 'TForm1' then
        Log:=  TLogInspector.Create(_WPARAM);
    end;

  Result := CallNextHookEx(MainHook, nCode, _WPARAM, _LPARAM);
end;


procedure InitLog; stdcall;
begin
  MainHook := SetWindowsHookEx(WH_CBT, @HookCallBack, 0, GetCurrentThreadId);
end;

procedure DoneLog; stdcall;
begin
  UnhookWindowsHookEx(MainHook);
end;

exports
   InitLog, DoneLog;

begin
end.

应用

type
  TForm1 = class(TForm)
    Button1: TButton;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

  procedure InitLog; stdcall;  external 'LogDll.dll' name 'InitLog';
  procedure DoneLog; stdcall;  external 'LogDll.dll' name 'DoneLog';

implementation

{$R *.dfm}



initialization
 InitLog;
finalization
 DoneLog;
end.
4

1 回答 1

3

您的测试用例过早地替换了窗口过程。操作系统在将第一条消息传递给实际窗口的窗口过程之前通知带有窗口创建的钩子会导致您在 VCL 完成设置窗体的窗口过程之前替换窗口过程。以下是您的窗口过程如何被 VCL 替换的关键事件的摘要:

  • 在项目中,VCL 将窗体的初始窗口程序设置InitWndProc在“控件”中。
  • 在项目中,VCL 调用CreateWindowHandle,而后者又调用CreateWindowEx.
  • 在 dll 中,操作系统通知您的钩子即将创建一个窗口,并且您对窗口过程进行子类化。
  • 在 dll 中,使用第一条消息 ( ) 调用被替换的窗口过程WM_GETMINMAXINFO
  • 在项目中,InitWndProc传递同样的消息,其中窗口过程被替换MainWndProc为窗体的方法。
  • 在 dll 中,重新替换的窗口过程不再被调用。

在'controls.pas' 中设置 dll 中SetWindowLongPtr的断点和项目中的断点以查看它的运行情况。SetWindowLongInitWndProc


使用WH_CALLWNDPROC钩子,您可能不需要子类化窗口即可记录发送给它的消息。例如:

function HookCallBack(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
  lpClassName : array [0 .. 256] of Char;
begin
  Result := CallNextHookEx(MainHook, nCode, wParam, lParam);
  if nCode >= 0 then begin
    GetClassName(PCWPStruct(lParam).hwnd, lpClassName, 256);
    if lpClassName = 'TForm1' then
      TFile.AppendAllText('C:\Delphi\log.txt', 'WndProc ' +
          IntToStr(PCWPStruct(lParam).message) + sLineBreak);
  end;
end;

procedure InitLog; stdcall;
begin
  MainHook := SetWindowsHookEx(WH_CALLWNDPROC, @HookCallBack, 0, GetCurrentThreadId);
end;

但是,如果必须,您可以在WM_NCCREATE.

于 2013-11-14T01:44:14.987 回答