如何将信息传递给使用 WinAPI 创建的 window 过程

How to pass information to window proc created using WinAPI

我需要创建一个 window 来处理消息 (WM_HOTKEY),所以我开始使用以下内容进行低级别处理,并使用 SetWindowLong 传递实例信息以在 [=25] 中使用=]proc.

fWindow:=CreateWindowEx(WS_EX_TOOLWINDOW,MsgWndClass.lpszClassName,'',WS_POPUP,0,0,0,0,0,0,HInstance,nil);
SetWindowLong(fWindow,GWL_USERDATA,NativeInt(Self));

并且 windowproc 是

class function TMessageWindow.WindowProc(hWnd: HWND; uMsg: Integer; wParam: WPARAM; lParam: LPARAM): Integer;
begin
  var I:=GetWindowLong(hWnd,GWL_USERDATA);
  if I=0 then
    Exit(DefWindowProc(hWnd,uMsg,wParam,lParam));

  Result:=TMessageWindow(I).HandleMessage(uMsg,wParam,lParam);
end;

当我尝试从 TMessageWindow 创建一个继承的 class 并且 HandleMessage 是虚拟的时,我的问题就出现了。
我发现虽然 HandleMessage 函数在继承 class 中被覆盖,但 TMessageWindow(I) 的类型转换正在调用基本方法。

在四处寻找这方面的例子后,我找不到任何使用 SetWindowLong 函数将信息传递给 windowproc 的例子,所以我现在认为必须有更好的方法。

首先,确保您的 class 方法也被标记为 static 以删除隐藏的 Self 参数,并且使用 stdcall 调用约定,如果你还没有这样做,例如:

class function WindowProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; static;

在那之后,如果你正在为 64 位编译,你的代码需要使用 (Get|Set)WindowLongPtr() 来代替,例如:

private
  fWindow: HWND;
fWindow := ...;
SetWindowLongPtr(fWindow, GWLP_USERDATA, LONG_PTR(Self));
class function TMessageWindow.WindowProc(hWnd: HWND; uMsg: Integer; wParam: WPARAM; lParam: LPARAM): Integer; stdcall;
begin
  var I := GetWindowLongPtr(hWnd, GWLP_USERDATA);
  if I <> 0 then
    Result := TMessageWindow(I).HandleMessage(uMsg, wParam, lParam)
  else
    Result := DefWindowProc(hWnd, uMsg, wParam, lParam);
end;

或者,使用 SetWindowSubclass(),例如:

private
  fWindow: HWND;
  class function SubclassWindowProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM; uIdSubclass: UINT_PTR; dwRefData: DWORD_PTR): LRESULT; stdcall; static;
uses
  ..., Commctrl;

fWindow := ...;
SetWindowSubclass(fWindow, @TMessageWindow.SubclassWindowProc, 1, DWORD_PTR(Self));
class function TMessageWindow.SubclassWindowProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM; uIdSubclass: UINT_PTR; dwRefData: DWORD_PTR): LRESULT; stdcall;
begin
  if uMsg = WM_NCDESTROY then
    RemoveWindowSubclass(hWnd, @TMessageWindow.SubclassWindowProc, uIdSubclass);
  Result := TMessageWindow(dwRefData).HandleMessage(uMsg, wParam, lParam);
  // have HandleMessage() call DefSubclassProc() for any unhandled messages...
end;

也就是说,使用虚拟消息过程创建消息 window 的更简单方法是改用 RTL 的 AllocateHWnd() 函数,例如:

private
  fWindow: HWND;
  procedure HandleMessage(var Message: TMessage); virtual;
// to create the window:
fWindow := AllocateHWnd(HandleMessage);
// to destroy the window:
DeallocateHWnd(fWindow);
procedure TMessageWindow.HandleMessage(var Message: TMessage);
begin
  with Message do
    Result := DefWindowProc(fWindow, Msg, WParam, LParam);
end;