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

能否利用启动进程时的TShellExecuteInfo记录关闭对应进程?

问题:通过ShellExecuteEx启动外部程序后,主程序关闭时如何关闭外部程序

我用以下Delphi代码在自身应用内启动其他程序:

type TShellState = class
  SEInfo: TShellExecuteInfo;
  AppName: String;
  Constructor Create(pSEInfo: TShellExecuteInfo; pAppName: String);
end;

Var
  ShellStates: TObjectStack<TShellState>;

Constructor TShellstate.Create(pSEInfo: TShellExecuteInfo; pAppName: string);
begin
  inherited Create;
  SEInfo := pSEInfo;
  AppName := pAppName;
end;

//In Form Create
ShellStates := TObjectStack<TShellState>.create;
ShellStates.OwnsObjects := true;

function DoExecute(Const ExecuteFile: string;
                   Const ParamString: string;
                   Modal: Boolean;
                   UseLocalDir: Boolean = false;
                   NoCloseProc: Boolean = true): Boolean;

Var SEInfo: TShellExecuteInfo;
    ExitCode: DWORD;

begin
  FillChar(SEInfo, SizeOf(SEInfo), 0) ;
  SEInfo.cbSize := SizeOf(TShellExecuteInfo) ;
  with SEInfo do begin
    fMask := SEE_MASK_DEFAULT;
    Wnd := Application.Handle;
    lpFile := PChar(ExecuteFile) ;
    lpParameters := PChar(ParamString) ;
    if UseLocalDir then
      lpDirectory := PChar(ExtractFilePath(ExecuteFile));
    nShow := SW_SHOWNORMAL;
  end;

  Result := ShellExecuteEx(@SEInfo);
  if result then begin
    if Modal then begin
      if SEInfo.hProcess <> 0 then begin
        WaitForSingleObject(SEInfo.hProcess, INFINITE);
        CloseHandle(SEInfo.hProcess);
      end;
      Result := true;
    end
    else if NoCloseProc then
      ShellStates.Push(TShellState.Create(SEInfo, ExecuteFile));
  end;
end;

当NoCloseProc为true时,我会把SEInfo记录存入栈中。主程序关闭时,我原本想通过SEInfo里的字段找到接收WM_CLOSE消息的窗口,示例代码如下:

while ShellStates.Count > 0 do begin
  ShellState := ShellStates.Peek;
  SendMessage(ShellState.SEInfo.Wnd, WM_CLOSE,0,0);
  ShellStates.Pop;
end

但实际发现hProcess和Wnd都是0,尝试用SEInfo.lpFile调用FindWindow API也返回0。有没有更直接的方法?


解决方案

1. 修改ShellExecuteEx的fMask参数获取进程句柄

当前设置的fMask := SEE_MASK_DEFAULT不会让ShellExecuteEx返回有效进程句柄,必须添加SEE_MASK_NOCLOSEPROCESS标记,这样SEInfo.hProcess才会被正确填充。修改后的代码片段:

with SEInfo do begin
  fMask := SEE_MASK_DEFAULT or SEE_MASK_NOCLOSEPROCESS; // 添加该标记
  Wnd := Application.Handle;
  // 其他原有代码不变
end;

2. 通过进程句柄优雅关闭外部程序

拿到有效hProcess后,无需查找窗口,可先尝试发送关闭信号,超时再强制终止:

while ShellStates.Count > 0 do begin
  ShellState := ShellStates.Pop;
  if ShellState.SEInfo.hProcess <> 0 then begin
    // 枚举进程对应的主窗口并发送WM_CLOSE
    EnumWindows(@EnumWindowsProc, LPARAM(ShellState.SEInfo.hProcess));
    // 等待进程退出,超时时间可自行调整
    if WaitForSingleObject(ShellState.SEInfo.hProcess, 5000) = WAIT_TIMEOUT then begin
      TerminateProcess(ShellState.SEInfo.hProcess, 0);
    end;
    CloseHandle(ShellState.SEInfo.hProcess);
  end;
end;

配合以下枚举窗口的回调函数,精准定位进程的主窗口:

function EnumWindowsProc(Wnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
  ProcessID: DWORD;
begin
  Result := True;
  GetWindowThreadProcessId(Wnd, @ProcessID);
  if ProcessID = DWORD(lParam) then begin
    // 筛选可见的主窗口
    if (GetWindowLongPtr(Wnd, GWL_STYLE) and (WS_VISIBLE or WS_OVERLAPPEDWINDOW)) = (WS_VISIBLE or WS_OVERLAPPEDWINDOW) then begin
      SendMessage(Wnd, WM_CLOSE, 0, 0);
      Result := False; // 找到后停止枚举
    end;
  end;
end;

3. 为什么FindWindow没用?

FindWindow需要窗口类名或标题作为参数,你传入的程序路径与窗口标识完全不匹配,因此返回0。这种方式可靠性远低于进程句柄,因为同一程序可能开多个窗口,或标题动态变化。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 08:47:41