如果我调用ShellExecuteEx,为什么GetExitCodeProcess说程序被终止,即使它仍在运行?

我想打开一个最初保存到SQL表的文件,但在调用ShellExecuteEx之前保存到磁盘。 一旦保存,我现在有一个有效的文件path,理论上应该能够使用这个function来实现我的目标。 我需要程序在适当的程序中打开文件,然后等待程序closures后再继续。 目前代码将启动正确的应用程序,并打开传递的文件,但它没有等待(我知道,因为我显示一条消息,以表明应用程序终止时)这样做,我写的应用程序,应该启动正确的程序closures。 它显示消息,然后启动程序。 我承认我并不完全理解ShellExecuteEx是如何工作的,并且使用了我在networking上发现的代码来结合我的代码来实现所需的结果。 下面你会find代码。 任何帮助将不胜感激。

procedure Fire(FileStr: String); var SEInfo: TShellExecuteInfo; ExitCode: DWORD; ExecuteFile, ParamString, StartInString: string; begin ExecuteFile:= FileStr; FillChar(SEInfo, SizeOf(SEInfo), 0) ; SEInfo.cbSize := SizeOf(TShellExecuteInfo); with SEInfo do begin fMask := SEE_MASK_NOCLOSEPROCESS; Wnd := Application.Handle; lpFile := PChar(ExecuteFile) ; nShow := SW_SHOWNORMAL; end; if ShellExecuteEx(@SEInfo) then begin repeat Application.ProcessMessages; GetExitCodeProcess(SEInfo.hProcess, ExitCode) ; until (ExitCode <> STILL_ACTIVE) or Application.Terminated; ShowMessage('App terminated') ; end else ShowMessage('Error starting App') ; end; 

我意识到我正在写入磁盘的文件还没有完成,这就是为什么消息出现在程序之前。 添加另一个应用程序。调用将文件写入磁盘后的处理消息解决了这个问题。 不过,被调用的应用程序打开时,GetExitCodeProcess仍然不会返回STILL_ACTIVE的值。

在做了更多的研究之后,我决定提出一个新的问题,这个问题更加突出了我正在努力实现的目标。 如果有人有兴趣跟随它,你可以在这里find它

我如何等待删除文件,直到我开始使用它的程序完成后?

就目前而言,问题中没有足够的信息来给你一个解释你的情况到底发生了什么的答案。 你还没有做足够的调试。 所以这个答案会更具说教性质。 我将试图教你如何诊断这种性质的问题。

首先,让我们清楚你的代码中的明显问题,这些问题对于实际问题来说是重要的,但却是外围的。

  • 您正在运行一个繁忙的等待循环。 这总是一个坏主意。 主GUI线程正在等待100%的CPU。 有两种明显的方法可以避免这种情况。 按照Marko的建议,使用一个线程执行对ShellExecuteEx的调用和随后的等待。 或者使用MsgWaitForMultipleObjects执行可以中断的等待,以便处理输入事件。
  • 如果ShellExecuteEx确实向您返回了一个进程句柄,则会泄漏它。 当一个API函数返回一个句柄时,你通常负责在你完成之后关闭它。 这需要调用CloseHandle 。 文档调用了这个: 调用应用程序负责关闭不再需要的句柄。
  • 您没有执行全面的错误检查。 您调用一个Win32 API函数并无法检查其返回值的错误。 后面会详细介绍,但是执行正确的错误检查至关重要。

现在,我们来仔细看看你在这个问题上报告的问题。 问题是对ShellExecuteEx的调用成功,文档被打开,但繁忙的等待在显示文档的进程关闭之前返回。 你的循环看起来像这样:

 repeat Application.ProcessMessages; GetExitCodeProcess(SEInfo.hProcess, ExitCode); until (ExitCode <> STILL_ACTIVE) or Application.Terminated; 

这里真的没有太多的代码。 如果这个循环比您预期的更早返回,怎么会发生? 当ExitCode <> STILL_ACTIVEApplication.TerminatedTrue时,循环终止。 首先要做的是隔离哪些条件导致循环终止。 一些简单的调试会产生这些信息。 我发现很难相信你不小心终止了你的应用程序,所以我将继续在Application.TerminatedFalse的基础上, ExitCode测试终止循环。

所以,让我们再看看GetExitCodeProcess的调用。 这对我来说很可疑,因为你没有执行任何错误检查。 现在,我碰巧有额外的信息,你可能缺乏。 特别是SEInfo.hProcess可能不包含进程句柄。 这使我更容易预见到这些问题,但您可以在调试器下学习所有这些。 再次从文档 :

新启动的应用程序的句柄。 该成员在返回时设置,并且除非将fMask设置为SEE_MASK_NOCLOSEPROCESS,否则始终为NULL。 即使fMask设置为SEE_MASK_NOCLOSEPROCESS,如果没有进程启动,hProcess将为NULL。 例如,如果要启动的文档是URL并且Internet Explorer的实例已在运行,则将显示该文档。 没有新的进程启动,hProcess将为NULL。

注意即使一个进程由于调用而启动,ShellExecuteEx并不总是返回一个hProcess。 例如,当您使用SEE_MASK_INVOKEIDLIST调用IContextMenu时,hProcess不会返回。

所以,也许发生的事情是你的文档正在被shell处理,使得返回给你的hProcess的值是NULL ,即0 。 当发生这种情况时,对GetExitCodeProcess的调用失败并返回False 。 这是你没有检查的错误条件。 您只需调用GetExitCodeProcess并忽略它是否成功。 如果这个调用没有成功,那么ExitCode将不会被分配一个有意义的值,而根本就是一个尝试读取ExitCode的错误。 它只有在GetExitCodeProcess返回True时才有意义。

还有另一种更加微妙的失败模式。 对ShellExecuteEx的调用可能返回一个有效的进程句柄。 但是,开始的过程通过将请求传递给另一个进程来处理,然后终止。 从你的角度来看,显示文档的进程仍在运行,但ShellExecuteEx给你的进程已经终止。 这通常发生在显示文档的进程正在运行的实例中。 例如,尝试在Delphi IDE打开时调用Fire函数传递一个.pas文件。 一个新的进程已经启动了,但是它立即把文件交给正在运行的Delphi IDE并终止。

好的,那就是我现在所能想到的。 我希望这可以帮助您追踪您场景中真正发生的事情。 这可能不是你希望听到的消息,因为我怀疑你会发现使用ShellExecuteEx并等待它返回的进程句柄的方法将不能满足你的需要。 不幸的是,使用shell打开一个文档要比最初看起来复杂得多。

你的repeat..until循环在这里是错误的。 避免使用Application.ProcessMessages()来使你的应用程序不被阻塞,因为这是不好的做法,可以/会导致不可预见的问题和怪异的错误。

相反,使用线程来等待应用程序终止:

 unit uSEWaitThread; interface uses Winapi.Windows, System.Classes; type TSEWaitThread = class(TThread) private FFile : String; FSESuccess: Boolean; FExitCode : DWORD; FParentWnd: HWND; protected procedure Execute; override; public constructor Create(const AFile: String; const AParentWindowHandle: HWND; const AOnDone: TNotifyEvent); property ExitCode : DWORD read FExitCode; property ShellExecuteSuccess: Boolean read FSESuccess; end; implementation uses Winapi.ShellApi; constructor TSEWaitThread.Create(const AFile: String; const AParentWindowHandle: HWND; const AOnDone: TNotifyEvent); begin FreeOnTerminate := TRUE; FFile := AFile; FParentWND := AParentWindowHandle; FSESuccess := FALSE; FExitCode := 0; OnTerminate := AOnDone; inherited Create; end; procedure TSEWaitThread.Execute; var exec_info: TShellExecuteInfo; begin FillChar(exec_info, SizeOf(TShellExecuteInfo), 0); exec_info.cbSize := SizeOf(TShellExecuteInfo); exec_info.fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI; exec_info.Wnd := FParentWnd; exec_info.lpFile := PChar(FFile); exec_info.nShow := SW_SHOWNORMAL; if ShellExecuteEx(@exec_info) then begin FSESuccess := TRUE; WaitForSingleObject(exec_info.hProcess, INFINITE); GetExitCodeProcess(exec_info.hProcess, FExitCode); end; end; end. 

以下行将启动线程:

 TSEWaitThread.Create('C:\test.txt', Handle, SEWaitThreadDone); 

以及处理线程结果的回调函数示例:

 procedure TForm1.SEWaitThreadDone(Sender: TObject); var se_wait_thread: TSEWaitThread; begin se_wait_thread := Sender as TSEWaitThread; if se_wait_thread.ShellExecuteSuccess then begin ShowMessage(Format('App exit code: %d', [se_wait_thread.ExitCode])); end else ShowMessage('Error Starting App'); end;