我想打开一个最初保存到SQL表的文件,但在调用ShellExecuteEx之前保存到磁盘。一旦保存,我现在有一个有效的文件路径,理论上应该能够使用此功能来实现我的目标。我需要程序在适当的程序中打开文件,并等到该程序关闭后再继续。目前代码将启动正确的应用程序并打开传递的文件,但它没有等待(我知道,因为我显示一条消息来指示应用程序何时终止)和我写的应该启动正确的程序关闭的应用程序。它显示消息,然后启动程序。我承认我并不完全理解ShellExecuteEx的工作原理,并且使用我在网上找到的代码和我的代码来实现所需的结果。您将在下面找到代码。任何帮助将不胜感激。
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;
我意识到我写入磁盘的文件还没有完成,这就是为什么消息出现在程序之前。在调用将文件写入磁盘后添加另一个application.Processmessages解决了这个问题。但是,当被叫应用程序打开时,GetExitCodeProcess仍然不会返回STILL_ACTIVE的值。
在做了更多研究之后,我决定开一个更加突出我想要完成的新问题。如果有人有兴趣关注它,你可以在这里找到它
How do I wait to delete a file until after the program I started has finished using it?
答案 0 :(得分:4)
就目前而言,问题中的信息不足以为您提供一个答案,可以准确解释您的方案中发生的情况。你还没有完成足够的调试。所以这个答案将具有更多的教学性质。我将尝试教你如何诊断这种性质的问题。
首先,让我们清除代码中明显的问题,这些问题很重要,但却是实际问题的外围。
ShellExecuteEx
的调用以及随后的等待,如Marko所建议的那样。或者使用MsgWaitForMultipleObjects
执行可以中断的等待以处理输入事件。ShellExecuteEx
确实向您返回了一个处理句柄,则会泄漏它。当API函数返回句柄时,通常负责在完成后关闭它。这需要拨打CloseHandle
。 documentation将其调出:调用应用程序负责在不再需要时关闭句柄。 现在,让我们仔细看看您在问题中报告的问题。问题是对ShellExecuteEx
的调用成功,文档被打开,但繁忙的等待在显示文档的进程关闭之前返回。繁忙的循环如下所示:
repeat
Application.ProcessMessages;
GetExitCodeProcess(SEInfo.hProcess, ExitCode);
until (ExitCode <> STILL_ACTIVE) or Application.Terminated;
这里真的没有很多代码。如果此循环返回的时间早于您的预期,那怎么会发生?循环在ExitCode <> STILL_ACTIVE
或Application.Terminated
为True
时终止。首先要做的是隔离哪些条件导致循环终止。一些简单的调试将产生该信息。我发现很难相信您不小心终止了您的申请,因此我将继续Application.Terminated
为False
并且ExitCode
测试终止循环。
那么,让我们再看一下GetExitCodeProcess
的电话。这对我来说很可疑,因为您没有执行任何错误检查。现在,我碰巧有额外的信息,你可能缺乏。特别是SEInfo.hProcess
可能不包含进程句柄。这使我更容易预测问题,但您可以在调试器下学习所有这些。再次从documentation开始:
新启动的应用程序的句柄。此成员已开启 除非fMask设置为,否则返回并始终为NULL SEE_MASK_NOCLOSEPROCESS。即使fMask设置为 SEE_MASK_NOCLOSEPROCESS,如果没有进程,则hProcess将为NULL 推出。例如,如果要启动的文档是URL和 Internet Explorer的实例已在运行,它将显示 文献。没有启动新进程,并且hProcess将为NULL。
注意 ShellExecuteEx并不总是返回hProcess,即使作为调用结果启动了进程。例如,一个 使用SEE_MASK_INVOKEIDLIST调用时,hProcess不返回 IContextMenu。
所以,也许正在发生的事情是shell正在处理您的文档,使hProcess
返回给您NULL
的值为0
,即GetExitCodeProcess
。发生这种情况时,对False
的调用将失败并返回GetExitCodeProcess
。这是你没有检查的错误情况。您只需调用ExitCode
并忽略它是否成功。如果该调用未成功,则ExitCode
将不会被赋予有意义的值,并且根本不会尝试阅读GetExitCodeProcess
。它仅在True
返回ShellExecuteEx
时才有意义。
还有另一种失败模式更加微妙。对ShellExecuteEx
的调用可能会返回有效的进程句柄。但是,启动的进程通过将请求传递给另一个进程然后终止来处理请求。从您的角度来看,显示文档的过程仍在运行,但Fire
给您的过程已终止。当显示文档的进程已在运行的实例时,通常会发生这种情况。例如,尝试在Delphi IDE打开时调用.pas
函数传递ShellExecuteEx
文件。启动了一个新进程,但它会立即将文件移交给正在运行的Delphi IDE并终止。
答案 1 :(得分:0)
此处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;