能否利用启动进程时的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
相关产品推荐
相关产品推荐

