Windows 如何从WndProc内部获取窗口句柄?

Windows 如何从WndProc内部获取窗口句柄?,windows,delphi,winapi,Windows,Delphi,Winapi,也许是个愚蠢的问题,但是 我正在编写一个类,该类应该负责将一个窗口(FGuestHWnd,从现在起)直观地锚定到一个“主机窗口”(FHostHWnd) FGuestHWnd和HostHWnd没有父/所有者/子关系 FGuestHWnd属于另一个进程-不在乎 FHostHWnd是VCLTWinControl的窗口句柄,因此它是我进程中的子窗口。它可以位于父/子树中的任何级别。例如,假设它是一个TPanel 现在,我必须“钩住”hookFHostHWnd的移动/调整大小,并在自定义计算后调用Se

也许是个愚蠢的问题,但是

我正在编写一个
,该类应该负责将一个窗口(
FGuestHWnd
,从现在起)直观地锚定到一个“主机窗口”(
FHostHWnd

  • FGuestHWnd
    HostHWnd
    没有父/所有者/子关系
  • FGuestHWnd
    属于另一个进程-不在乎
  • FHostHWnd
    是VCL
    TWinControl
    的窗口句柄,因此它是我进程中的子窗口。它可以位于父/子树中的任何级别。例如,假设它是一个
    TPanel
现在,我必须“钩住”hook
FHostHWnd
的移动/调整大小,并在自定义计算后调用
SetWindowPos(FGuestHWnd…

调整大小很简单:我可以使用
SetWindowLong(FHostHWnd,GWL_WNDPROC,…)
来“重定向”
FHostHWnd
的WndProc到我的自定义WindowProcedure和trap
WM_WINDOWPOSCHANGING
。当它的一个祖先调整大小时,此消息会自动发送到
FHostHWnd
,因为
FHostHWnd
与客户端对齐

移动,如果我没有遗漏什么的话,是有点棘手的,因为如果我移动主窗体
FHostHWnd
,它并没有真正移动。它相对于其父窗体保持相同的位置。因此它不会以任何方式通知祖先的移动

我的解决方案是将任何祖先的WndProc“重定向”到自定义窗口过程,并捕获WM_WindowPosChangeing以仅用于“移动”消息。 在这种情况下,我可以用自定义消息通知
FHostHWnd
。 我的类中的一些字段将跟踪Win句柄链、原始WndProc加法和新WndProc地址

下面是一些代码来解释我的结构:

TMyWindowHandler = class(TObject)
private
  FHostAncestorHWndList: TList;
  FHostHWnd: HWND;
  FGuestHWnd: HWND;
  FOldHostAncestorWndProcList: TList;
  FNewHostAncestorWndProcList: TList;
  //...
  procedure HookHostAncestorWindows;
  procedure UnhookHostAncestorWindows;
  procedure HostAncestorWndProc(var Msg: TMessage);
end;

procedure TMyWindowHandler.HookHostAncestorWindows;
var
  ParentHWnd: HWND;
begin
  ParentHWnd := GetParent(FHostHWnd);
  while (ParentHWnd > 0) do
  begin
    FHostAncestorHWndList.Insert(0, Pointer(ParentHWnd));
    FOldHostAncestorWndProcList.Insert(0, TFarProc(GetWindowLong(ParentHWnd,     GWL_WNDPROC)));
    FNewHostAncestorWndProcList.Insert(0, MakeObjectInstance(HostAncestorWndProc));
    Assert(FOldHostAncestorWndProcList.Count = FHostAncestorHWndList.Count);
    Assert(FNewHostAncestorWndProcList.Count = FHostAncestorHWndList.Count);
    if (SetWindowLong(ParentHWnd, GWL_WNDPROC, LongInt(FNewHostAncestorWndProcList[0])) = 0) then
      RaiseLastOSError;
    ParentHWnd := GetParent(FHostHWnd);
  end;
end;
这是处理程序:

procedure TMyWindowHandler.HostAncestorWndProc(var Msg: TMessage);
var
  pNew: PWindowPos;
begin
  case Msg.Msg of
    WM_DESTROY: begin
      UnHookHostAncestorWindows;
    end;
    WM_WINDOWPOSCHANGING: begin
      pNew := PWindowPos(Msg.LParam);
      // Only if the window moved!
      if ((pNew.flags and SWP_NOMOVE) = 0) then
      begin
        //
        // Do whatever
        //
      end;
    end;
  end;
  Msg.Result := CallWindowProc(???, ???, Msg.Msg, Msg.WParam, Msg.LParam );
end;
我的问题是:

当我最终调用
CallWindowProc
时,如何从WindowProcedure内部获取窗口句柄?
(如果我有窗口句柄,我也可以在
FoldHostStorWndProcList
中找到它,然后在
FHostStorHwdList
中查找右旧WndProc指针) 或者,作为一种替代方法,如何获取当前方法指针,以便我可以在
fnewwhostatcestorwndproclist
中找到它,并在
FHostAncestorHWndList
中查找HWND

或者你建议其他的解决方案

请注意,我希望所有内容都面向HWND,而不是VCL/TWinControl。

换句话说,我的应用程序应该只实例化TMyWindowHandler,并将两个
HWND
s(主机和来宾)传递给它.

我个人不会在这里使用
MakeObjectInstance
MakeObjectInstance
如果您希望将实例绑定到单个窗口句柄,则非常有用。
MakeObjectInstance
的神奇之处在于生成一个thunk,将窗口过程调用转发给实例方法。这样做时,窗口句柄就不可用了传递给实例方法,因为假设实例已经知道其关联的窗口句柄。对于
TWinControl
MakeObjectInstance
的主要用例,肯定是这样的

现在,您正在将它绑定到多个窗口句柄。当实例方法执行时,您无法知道许多窗口句柄中的哪一个与此方法执行相关。这就是问题的症结所在

我的建议是放弃
MakeObjectInstance
,因为它不满足您的需要。相反,请定义此表单的普通窗口过程:

function WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; 
  lParam: LPARAM): LRESULT; stdcall;
当您实现这样的窗口过程时,您确实会收到一个窗口句柄,正如您所希望的那样

您可能需要保留
TMyWindowHandler
实例的全局列表,以便查找与传递给窗口过程的窗口关联的
TMyWindowHandler
实例。或者,您可以使用将一些数据与窗口关联


请注意,对windows进行子分类的方式存在各种问题。提供了
setWindowsSubClass
函数以避免这些问题。此处的详细信息:。

可以将用户定义的数据传递给
MakeObjectInstance()
。它接受一个闭包作为输入,并且可以使用
TMethod
记录操作闭包,因此您可以将其
数据
字段设置为指向您想要的任何对象,并且可以通过方法体中的
Self
指针访问它。例如:

type
  PMyWindowHook = ^TMyWindowHook;
  TMyWindowHook = record
    Wnd: HWND;
    OldWndProc: TFarProc;
    NewWndProc: Pointer;
    Handler: TMyWindowHandler;
  end;

  TMyWindowHandler = class
  private
    FHostAncestorHWndList: TList;
    FHostAncestorWndProcList: TList;
    FHostHWnd: HWND;
    FGuestHWnd: HWND;
    //...
    procedure HookHostAncestorWindows;
    procedure UnhookHostAncestorWindows;
    procedure HostAncestorWndProc(var Msg: TMessage);
  end;

procedure TMyWindowHandler.HookHostAncestorWindows;
var
  ParentHWnd: HWND;
  Hook: PMyWindowHook;
  NewWndProc: Pointer;
  M: TWndMethod;
begin
  ParentHWnd := GetParent(FHostHWnd);
  while ParentHWnd <> 0 do
  begin
    M := HostAncestorWndProc;
    New(Hook);
    try
      TMethod(M).Data := Hook;
      Hook.Hwnd := ParentHWnd;
      Hook.OldWndProc := TFarProc(GetWindowLong(ParentHWnd, GWL_WNDPROC));
      Hook.NewWndProc := MakeObjectInstance(M);
      Hook.Handler := Self;
      FHostAncestorWndProcList.Insert(0, Hook);
      try
        SetLastError(0);
        if SetWindowLongPtr(ParentHWnd, GWL_WNDPROC, LONG_PTR(Hook.NewWndProc)) = 0 then
        begin
          if GetLastError() <> 0 then
          begin
            FreeObjectInstance(Hook.NewWndProc);
            RaiseLastOSError;
          end;
        end;
      except
        FHostAncestorWndProcList.Delete(0);
        raise;
      end;
    except
      Dispose(Hook);
      raise;
    end;
    ParentHWnd := GetParent(ParentHWnd);
  end;
end;

procedure TMyWindowHandler.UnhookHostAncestorWindows;
var
  Hook: PMyWindowHook;
begin
  while FHostAncestorWndProcList.Count > 0
  begin
    Hook := PMyWindowHook(FHostAncestorWndProcList.Items[0]);
    FHostAncestorWndProcList.Delete(0);
    SetWindowLongPtr(Hook.Hwnd, GWL_WNDPROC, LONG_PTR(Hook.OldWndProc));
    FreeObjectInstance(Hook.NewWndProc);
    Dispose(Hook);
  end;
end;

procedure TMyWindowHandler.HostAncestorWndProc(var Msg: TMessage);
var
  Hook: PMyWindowHook;
  pNew: PWindowPos;
begin
  Hook := PMyWindowHook(Self);
  case Msg.Msg of
    WM_DESTROY: begin
      Msg.Result := CallWindowProc(Hook.Wnd, Hook.OldWndProc, Msg.Msg, Msg.WParam, Msg.LParam);
      Hook.Handler.FHostAncestorWndProcList.Remove(Hook);
      SetWindowLongPtr(Hook.Hwnd, GWL_WNDPROC, LONG_PTR(Hook.OldWndProc));
      FreeObjectInstance(Hook.NewWndProc);
      Dispose(Hook);
      Exit;
    end;
    WM_WINDOWPOSCHANGING: begin
      pNew := PWindowPos(Msg.LParam);
      // Only if the window moved!
      if (pNew.flags and SWP_NOMOVE) = 0 then
      begin
        //
        // Do whatever
        //
      end;
    end;
  end;
  Msg.Result := CallWindowProc(Hook.Wnd, Hook.OldWndProc, Msg.Msg, Msg.WParam, Msg.LParam);
end;
type
  TMyWindowHandler = class
  private
    FHostAncestorHWndList: TList;
    FHostAncestorWndProcList: TList;
    FHostHWnd: HWND;
    FGuestHWnd: HWND;
    //...
    procedure HookHostAncestorWindows;
    procedure UnhookHostAncestorWindows;
    class function HostAncestorWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData): LRESULT; stdcall; static;
  end;

procedure TMyWindowHandler.HookHostAncestorWindows;
var
  ParentHWnd: HWND;
begin
  ParentHWnd := GetParent(FHostHWnd);
  while ParentHWnd <> 0 do
  begin
    FHostAncestorWndProcList.Insert(0, Pointer(ParentWnd));
    try
      if not SetWindowSubclass(ParentWnd, @HostAncestorWndProc, 1, DWORD_PTR(Self)) then
        RaiseLastOSError;
    except
      FHostAncestorWndProcList.Delete(0);
      raise;
    end;
    ParentHWnd := GetParent(ParentHWnd);
  end;
end;

procedure TMyWindowHandler.UnhookHostAncestorWindows;
begin
  while FHostAncestorWndProcList.Count > 0 do
  begin
    RemoveWindowSubclass(HWND(FHostAncestorWndProcList.Items[0]), @HostAncestorWndProc, 1);
    FHostAncestorWndProcList.Delete(0);
  end;
end;

class function TMyWindowHandler.HostAncestorWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData): LRESULT; stdcall;
var
  pNew: PWindowPos;
begin
  case uMsg of
    WM_NCDESTROY: begin
      RemoveWindowSubclass(hWnd, @HostAncestorWndProc, 1);
      TMyWindowHandler(dwRefData).FHostAncestorWndProcList.Remove(Pointer(hWnd));
    end;
    WM_WINDOWPOSCHANGING: begin
      pNew := PWindowPos(Msg.LParam);
      // Only if the window moved!
      if (pNew.flags and SWP_NOMOVE) = 0 then
      begin
        //
        // Do whatever
        //
      end;
    end;
  end;
  Result := DefSubclassProc(hWnd, uMsg, wParam, lParam);
end;

谢谢你,大卫。但是
MakeObjectInstance
不是专门用来解决这个问题的吗?它似乎就是VCL本身绑定
TWinControl
WndProc
的确切方式。我已经对此进行了扩展。
MakeObjectInstance
是你的问题。
AllocateHwnd
TWinControl.Create
到form窗口和实例之间的一对一关系。您的问题是多对一的窗口到实例关系。因此,
MakeObjectInstance
一点好处都没有。+1获取有关
MakeObjectInstance
的有趣信息。我最终使用了
setWindowsSubclass
。更干净了。尽管有其他问题,这还是一个价值关于如何使用
setWindowsSubClass
的示例(在Delphi中)。