如何从WndProc内部获取Window Handle?

时间:2014-07-02 13:59:59

标签: windows delphi winapi

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

我正在写一个class,它应该保持一个窗口(FGuestHWnd,从现在开始)视觉上锚定到一个"主机窗口" (FHostHWnd)。

  • FGuestHWndHostHWnd没有父母/所有者/子女关系。
  • FGuestHWnd属于另一个流程 - 不在乎。
  • FHostHWnd是VCL TWinControl的Window句柄,因此它是我进程中的子窗口。它可以位于父/子树内的任何级别。例如,我们说它是TPanel

现在我必须"勾" FHostHWnd移动/调整大小并在我的自定义计算后调用SetWindowPos(FGuestHWnd...

调整大小非常简单:我可以使用SetWindowLong(FHostHWnd, GWL_WNDPROC, ...)来重定向" FHostHWnd我的自定义WindowPorcedure和陷阱WM_WINDOWPOSCHANGING的WndProc。当其中一个祖先调整大小时,此消息会自动发送到FHostHWnd,因为FHostHWnd是客户端对齐的。

移动,如果我没有遗漏某些东西,有点棘手,因为如果我移动主要形式FHostHWnd并没有真正感动。它保持相对于其父级的相同位置。因此,它不会以任何方式通知祖先的运动。

我的解决方案是"重定向"任何ANCESTOR的WndProc到自定义窗口过程和陷阱WM_WINDOWPOSCHANGING用于"移动"仅邮件。 在这种情况下,我可以通过自定义消息通知FHostHWnd。 我班上的一些领域将跟踪Win Handles链,原始WndProc addesses和新的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;

这是The Handler:

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中获取窗口句柄? (如果我有窗口句柄,我也可以在FOldHostAncestorWndProcList中找到它,然后在FHostAncestorHWndList中查找正确的Old-WndProc指针 或者,作为替代方案,如何获取CURRENT方法指针,以便我可以在FNewHostAncestorWndProcList中找到它并在FHostAncestorHWndList中查找HWND。

或者您是否建议其他解决方案?

请注意,我希望保持一切以HWND为导向,而不是VCL / TWinControl感知。
换句话说,我的应用程序应该只实例化TMyWindowHandler传递给它的两个HWND(主机和访客)。

2 个答案:

答案 0 :(得分:6)

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

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; SetWindowSubClass()更好。SetWindowLong(GWL_WNDPROC)。钩子过程为您提供HWND,您可以指定用户定义的数据。不需要黑客。例如:

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;

答案 1 :(得分:5)

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

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

我的建议是放弃MakeObjectInstance,因为它无法满足您的需求。相反,定义此窗体的普通窗口过程:

function WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; 
  lParam: LPARAM): LRESULT; stdcall;

当你实现这样的窗口过程时,你会收到一个窗口句柄,如你所愿。

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

请注意,您对Windows进行子类化的方式存在各种问题。提供SetWindowSubclass函数是为了避免这些问题。更多详情:Subclassing Controls