【问题标题】:Is there a way to get a VCL Control's name through the windows API?有没有办法通过 windows API 获取 VCL 控件的名称?
【发布时间】:2013-03-20 20:30:51
【问题描述】:

我有一个 VCL 控件的 Hwnd,它位于另一个进程的窗口中。有没有办法通过 Windows API 获取该控件的 VCL 名称(TControl.Name 属性)? 我需要这个名称,因为该窗口上有多个 TEdit,我需要确定我想要的那个,以便向它发送 WM_SETTEXT 消息。

这两个应用程序都是使用 Delphi 2010 构建的。

【问题讨论】:

  • 如果您控制代码,您可以通过返回名称来响应用户定义的窗口消息
  • 或者简单地定义你自己的 WM_USER 消息集
  • 您对这两个应用程序有控制权吗?你能重建两个吗?
  • 我控制第一个应用程序并对另一个应用程序有一些控制权,但是更改第二个应用程序将违背这样做的目的:与第二个应用程序交互而不更改它。

标签: delphi winapi


【解决方案1】:

Delphi 有内置函数FindControl(),它返回指定hWnd 的TWinControl。但它适用于同一个 VCL 实例。我觉得你应该调查一下。在您获得指向 TWinControl 对象的指针后,其名称(字符串)位于 +8 偏移处。您可以尝试 ReadProcessMemory 来读取它。这里的主要问题是创建适合您需要的 FindControl() 版本。

编辑:(终于明白了:D)调用GetWinControlName函数

// Get Pointer to TWinControl in another process
function GetWinControl(Wnd: HWND; out ProcessId: THandle): Pointer;
var
  WindowAtomString: String;
  WindowAtom: ATOM;
begin
  if GetWindowThreadProcessId(Wnd, ProcessId) = 0 then RaiseLastOSError;

  // This is atom for remote process (See controls.pas for details on this)
  WindowAtomString := Format('Delphi%.8X',[ProcessID]);
  WindowAtom := GlobalFindAtom(PChar(WindowAtomString));
  if WindowAtom = 0 then RaiseLastOSError;

  Result := Pointer(GetProp(Wnd, MakeIntAtom(WindowAtom)));
end;

function GetWinControlName(Wnd: HWND): string;
var
  ProcessId: THandle;
  ObjSelf: Pointer;
  Buf: Pointer;
  bytes: Cardinal;
  destProcess: THandle;
begin
  ObjSelf := GetWinControl(Wnd, ProcessId);

  destProcess := OpenProcess(PROCESS_VM_READ, TRUE, ProcessId);
  if destProcess = 0 then RaiseLastOSError;

  try
    GetMem(Buf, 256);
    try
      if not ReadProcessMemory(destProcess, Pointer(Cardinal(ObjSelf) + 8), Buf, 4, bytes) then RaiseLastOSError;
      if not ReadProcessMemory(destProcess, Pointer(Cardinal(Buf^)), Buf, 256, bytes) then RaiseLastOSError;
      Result := PChar(Buf);
    finally
      FreeMem(Buf);
    end;
  finally
    CloseHandle(destProcess);
  end;
end;

【讨论】:

  • 现在我只需要找到一个在另一个进程中工作的FindControl 函数:/
  • 尝试使用内部'ObjectFromHWnd'函数'Result := Pointer(SendMessage(Handle, RM_GetObjectInstance, 0, 0))'的这一部分。我认为您应该删除所有这些 ProcessId 检查。
  • 偏移量取决于用于创建其他控件的 Delphi 版本。 TObject.InstanceSize 在引入 System.TMonitor 时发生了变化。
  • @Samaliani 有一个 FreeMem(Buf, 256) 和一个 CloseHandle(destProcess),但这个概念很好用,谢谢。
  • @Daniel:仅供参考,RM_GetObjectInstance 问题最终在 XE3 中得到修复,为所有 VCL 进程使用相同的消息 ID,而不是每个进程的唯一消息 ID。
【解决方案2】:

不,没有可产生控件名称的 Windows API 函数。那是一个私有的 Delphi 实现细节。

如果您控制目标进程的代码,那么显然您可以实现某种形式的 IPC 来解决问题。否则,任何产生控件名称的解决方案都将涉及相当卑鄙的黑客攻击。一种方法是将使用相同版本的运行时构建的 DLL 注入进程。获取该 DLL 以从 HWND 中找到 VCL 控件引用并读出名称。这方面有很多变体,@Samaliani 的回答提供的出色的ReadProcessMemory 诡计是您必须跳过的典型环节。

但是,我可以想出一个更简单的解决方案来解决您的问题。找到所有编辑控件的句柄并使用这些句柄接收控件的坐标。编辑控件的相对位置将足以确定哪个是所需的目标。请阅读下面@dthorpe 的 cmets 以获得更多有用的想法。

【讨论】:

  • 一个人的卑鄙黑客是另一个人的专利测试自动化框架。
  • @dthorpe 确实如此。你喜欢我的替代解决方案吗?
  • 比较窗口矩形足以手动确认(“这是它吗?”),但长期不可靠。窗口大小和位置可以随着本地化而改变。一旦目标被识别,随着时间的推移,记住它的子窗口索引或标签顺序索引可能是一个更可靠的标签。在本地化中,Tab 顺序通常不会改变。
  • @Daniel 我在 1990 年代为 VCL 实现的测试自动化系统获得了 Borland 的专利。
  • @dthorpe 所以你就是那个人,我知道我以前听说过这个名字。你是一个伟大的软件工程师,我是你的粉丝!当我阅读 Delphi 及其内部结构时,你的名字时不时会出现。
猜你喜欢
  • 1970-01-01
  • 2019-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-04
  • 2013-01-08
  • 1970-01-01
  • 2022-01-26
相关资源
最近更新 更多