You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

根据进程ID获取窗口句柄结果不一致的问题排查

Delphi 托管外部应用时窗口句柄获取失败的修复方案

问题现象

在Delphi窗体中托管外部应用时,程序表现不一致:启动notepad.exe时无法找到匹配进程ID的窗口句柄,但自行开发的Delphi应用能被正常识别。

核心问题原因

  1. 窗口创建时机未同步:WaitForInputIdle仅等待进程进入空闲状态,但记事本这类轻量应用可能在此时窗口尚未完成注册,导致EnumWindows执行时无法捕获到句柄。
  2. 窗口过滤逻辑缺失:原代码未过滤不可见窗口或非顶层窗口,同时注释掉了主窗口判断,反而可能遗漏有效窗口;另外64位环境下指针传递存在截断问题。
  3. 单次枚举的局限性:仅执行一次EnumWindows,无法应对窗口延迟创建的情况。

修复后的完整代码

修正窗口枚举回调函数

function enum_windows_callback(Handle: HWND; LParam: LPARAM): BOOL; stdcall;
begin
  Result := True;
  var obj: TfHostApplication := TfHostApplication(LParam);
  var process_id: DWORD := 0;
  GetWindowThreadProcessId(Handle, @process_id);
  
  // 仅收集可见的顶层主窗口(排除子窗口、隐藏窗口)
  if (obj.process_id = process_id) and IsWindowVisible(Handle) and (GetWindow(Handle, GW_OWNER) = 0) then
    obj.AppWindowHandles.Add(Handle);
end;

修正应用启动与句柄收集逻辑

procedure TfHostApplication.HostApplication(fn: string);
var
  Rec: TShellExecuteInfo;
  Title: string;
  WaitLoop: Integer;
const
  AVerb = 'open';
  AParams = '';
  ADir = '';
begin
  FillChar(Rec, SizeOf(Rec), #0);

  Rec.cbSize       := SizeOf(Rec);
  Rec.fMask        := SEE_MASK_NOCLOSEPROCESS;
  Rec.lpVerb       := PChar(AVerb);
  Rec.lpFile       := PChar(fn);
  Rec.lpParameters := PChar(AParams);
  Rec.lpDirectory  := PChar(ADir);
  Rec.nShow        := SW_HIDE;

  if ShellExecuteEx(@Rec) then
  begin
    ProcessHandle := Rec.hProcess;
    process_id := GetProcessId(Rec.hProcess);
    AppWindowHandles.Clear;

    // 循环等待窗口创建,最多等待10秒(20次×500ms)
    WaitLoop := 0;
    while (AppWindowHandles.Count = 0) and (WaitLoop < 20) do
    begin
      WaitForInputIdle(Rec.hProcess, 500);
      EnumWindows(@enum_windows_callback, LPARAM(Self));
      Inc(WaitLoop);
      Sleep(100);
    end;

    if AppWindowHandles.Count > 1 then
    begin
      lbWindows.Items.Clear;
      for var hwd in AppWindowHandles do
      begin
        var Len := GetWindowTextLength(hwd) + 1;
        SetLength(Title, Len);
        GetWindowText(hwd, PChar(Title), Len);
        Title := Trim(Title);
        lbWindows.Items.Add(Title);
      end;
      PanelSel.Visible := True;
      PanelSel.Align := alClient;
    end
    else if AppWindowHandles.Count > 0 then
    begin
      SelectedWindowHandle := AppWindowHandles[0];
      ShowAppInWindow;
    end
    else
      DoCloseView;
  end;
end;

优化窗口托管样式调整

procedure TfHostApplication.ShowAppInWindow;
begin
  Windows.SetParent(SelectedWindowHandle, Handle);

  // 移除窗口边框、标题栏等样式,适配容器
  var Style := GetWindowLongPtr(SelectedWindowHandle, GWL_STYLE);
  Style := Style and not (WS_BORDER or WS_DLGFRAME or WS_THICKFRAME or WS_CAPTION or WS_SYSMENU);
  SetWindowLongPtr(SelectedWindowHandle, GWL_STYLE, Style);

  // 强制调整窗口大小并刷新布局
  SetWindowPos(SelectedWindowHandle, 0, 0, 0, ClientWidth, ClientHeight, SWP_NOZORDER or SWP_FRAMECHANGED);
  ShowWindow(SelectedWindowHandle, SW_SHOW);
end;

关键修复点说明

  • 兼容32/64位环境:使用LPARAM(Self)传递对象指针,避免原代码中integer(@Self)在64位下的指针截断问题。
  • 可靠的窗口等待逻辑:通过循环等待+多次枚举,确保窗口完全创建后再收集句柄,解决单次WaitForInputIdle的局限性。
  • 精准过滤有效窗口:只收集可见的顶层主窗口,避免收集到进程内的隐藏窗口、子窗口等无效句柄。

内容的提问来源于stack exchange,提问作者Martin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 18:05:01