如何调用应用程序并等待它退出的高质量代码示例

时间:2014-04-12 17:48:55

标签: delphi delphi-xe2 shellexecute

使用:Delphi XE2; Windows 32位VCL应用程序

在我的Delphi应用程序中,我需要使用ShellExecute调用一个应用程序并等到它完成后再继续。

我在ShellExecute的SO上看到了很多关于MsgWaitForMultipleObjects的例子,但是不知道哪一个是最好的,因为他们大多是做不推荐的事情,即。还使用了许多人不推荐的Application.ProcessMessages。

我在NFX中看到post的答案,它不使用Application.ProcessMessages,但不确定它是否正确或最佳,因此这个问题。

如果您能提供高质量的代码示例,我们将非常高兴。

任何答案的TIA。

3 个答案:

答案 0 :(得分:4)

ShellExecuteEx()CreateProcess()都会返回您可以等待的HANDLE。当产生的进程退出时,HANDLE会发出信号。

如果您必须在等待时抽取消息队列,请使用MsgWaitForMultipleObjects()检测新消息何时等待处理。否则,您可以改为使用WaitForSingleObject()

NFX的回答并没有使用Application.ProcessMessages(),但仍会提供消息,因此根本问题仍然存在。如果您在主线程中进行等待,除非您不介意向用户(或操作系统)呈现无响应的UI,否则无法避免这种情况。您可以选择在工作线程中等待,然后您不需要在等待时抽取消息,并且您的UI不会被阻止。如果需要,您可以在等待时禁用UI或显示状态UI。

答案 1 :(得分:2)

function StartProcess(Exename: string; CmdLineArgs: string = ''; ShowWindow: boolean = True;
  WaitForFinish: boolean = False): integer;
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
  CreateOK: boolean;
begin
  // Simple wrapper for the CreateProcess command
  // returns the process id of the started process.
  FillChar(StartInfo, SizeOf(TStartupInfo), #0);
  FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
  StartInfo.cb := SizeOf(TStartupInfo);

  if not(ShowWindow) then
  begin
    StartInfo.dwFlags := STARTF_USESHOWWINDOW;
    StartInfo.wShowWindow := SW_HIDE;
  end;

  CreateOK := CreateProcess(nil, PChar(Exename + ' ' + CmdLineArgs), nil, nil, False,
    CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS, nil, nil, StartInfo, ProcInfo);

  Result := ProcInfo.dwProcessId;

  if CreateOK then
  begin
    // may or may not be needed. Usually wait for child processes
    if WaitForFinish then
      WaitForSingleObject(ProcInfo.hProcess, Infinite);
  end
  else
  begin
    // ShowMessage('Unable to run '+ProgramName);
    SysErrorMessage(GetLastError());
  end;

  // close process & thread handles
  CloseHandle(ProcInfo.hProcess);
  CloseHandle(ProcInfo.hThread);
end;

答案 2 :(得分:2)

我使用这些函数异步执行子进程,并在进程终止时回调它。它的工作原理是创建一个等待进程终止的线程,然后通过给定的事件方法回调主程序线程。请注意,您的程序在子进程运行时继续运行,因此您需要某种形式的逻辑来防止产生子进程的无限发生。

UNIT SpawnFuncs;

INTERFACE

{$IF CompilerVersion >= 20 }
  {$DEFINE ANONYMOUS_METHODS }
{$ELSE }
  {$UNDEF ANONYMOUS_METHODS }
{$ENDIF }

TYPE
  TSpawnAction  = (saStarted,saEnded);
  TSpawnArgs    = RECORD
                    Action      : TSpawnAction;
                    FileName    : String;
                    PROCEDURE   Initialize(Act : TSpawnAction ; CONST FN : String); INLINE;
                    CLASS FUNCTION Create(Act : TSpawnAction ; CONST FN : String) : TSpawnArgs; static;
                  END;
  {$IFDEF ANONYMOUS_METHODS }
    TSpawnEvent = REFERENCE TO PROCEDURE(Sender : TObject ; CONST Args : TSpawnArgs);
  {$ELSE }
    TSpawnEvent = PROCEDURE(Sender : TObject ; CONST Args : TSpawnArgs) OF OBJECT;
  {$ENDIF }

FUNCTION ShellExec(CONST FileName,Tail : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL) : BOOLEAN; OVERLOAD;
FUNCTION ShellExec(CONST FileName : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL) : BOOLEAN; OVERLOAD;
FUNCTION ShellExec(CONST FileName : String ; VAR EndedFlag : BOOLEAN) : BOOLEAN; OVERLOAD;
FUNCTION ShellExec(CONST FileName,Tail : String ; VAR EndedFlag : BOOLEAN) : BOOLEAN; OVERLOAD;

PROCEDURE ShellExecExcept(CONST FileName : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL); OVERLOAD:
PROCEDURE ShellExecExcept(CONST FileName,Tail : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL); OVERLOAD;
PROCEDURE ShellExecExcept(CONST FileName : String ; VAR EndedFlag : BOOLEAN); OVERLOAD;
PROCEDURE ShellExecExcept(CONST FileName,Tail : String ; VAR EndedFlag : BOOLEAN); OVERLOAD;

IMPLEMENTATION

USES Windows,SysUtils,Classes,ShellApi;

TYPE
  TWaitThread   = CLASS(TThread)
                    CONSTRUCTOR Create(CONST FileName : String ; ProcessHandle : THandle ; Event : TSpawnEvent ; Sender : TObject); REINTRODUCE; OVERLOAD;
                    CONSTRUCTOR Create(CONST FileName : String ; ProcessHandle : THandle ; EndedFlag : PBoolean); OVERLOAD;
                    PROCEDURE   Execute; OVERRIDE;
                    PROCEDURE   DoEvent(Action : TSpawnAction);
                  PRIVATE
                    Handle      : THandle;
                    Event       : TSpawnEvent;
                    EndedFlag   : PBoolean;
                    FN          : String;
                    Sender      : TObject;
                    {$IFNDEF ANONYMOUS_METHODS }
                      Args      : TSpawnArgs;
                      PROCEDURE RunEvent;
                    {$ENDIF }
                  END;

CONSTRUCTOR TWaitThread.Create(CONST FileName : String ; ProcessHandle : THandle ; Event : TSpawnEvent ; Sender : TObject);
  BEGIN
    INHERITED Create(TRUE);
    Handle:=ProcessHandle; Self.Event:=Event; FN:=FileName; Self.Sender:=Sender; FreeOnTerminate:=TRUE;
    Resume
  END;

{$IFNDEF ANONYMOUS_METHODS }
PROCEDURE TWaitThread.RunEvent;
  BEGIN
    Event(Sender,Args)
  END;
{$ENDIF }

CONSTRUCTOR TWaitThread.Create(CONST FileName : String ; ProcessHandle : THandle ; EndedFlag : PBoolean);
  BEGIN
    INHERITED Create(TRUE);
    Handle:=ProcessHandle; EndedFlag^:=FALSE; Self.EndedFlag:=EndedFlag; FreeOnTerminate:=TRUE;
    Resume
  END;

PROCEDURE TWaitThread.DoEvent(Action : TSpawnAction);
  BEGIN
    IF Assigned(EndedFlag) THEN
      EndedFlag^:=(Action=saEnded)
    ELSE BEGIN
      {$IFDEF ANONYMOUS_METHODS }
        Synchronize(PROCEDURE BEGIN Event(Sender,TSpawnArgs.Create(Action,FN)) END)
      {$ELSE }
        Args:=TSpawnArgs.Create(Action,FN);
        Synchronize(RunEvent)
      {$ENDIF }
    END
  END;

PROCEDURE TWaitThread.Execute;
  BEGIN
    DoEvent(saStarted);
    WaitForSingleObject(Handle,INFINITE);
    CloseHandle(Handle);
    DoEvent(saEnded)
  END;

FUNCTION ShellExec(CONST FileName,Tail : String ; Event : TSpawnEvent ; Sender : TObject ; EndedFlag : PBoolean) : BOOLEAN; OVERLOAD;
  VAR
    Info  : TShellExecuteInfo;
    PTail : PChar;

  BEGIN
    ASSERT(NOT (Assigned(Event) AND Assigned(EndedFlag)),'ShellExec called with both Event and EndedFlag!');
    IF Tail='' THEN PTail:=NIL ELSE PTail:=PChar(Tail);
    FillChar(Info,SizeOf(TShellExecuteInfo),0);
    Info.cbSize:=SizeOf(TShellExecuteInfo);
    Info.fMask:=SEE_MASK_FLAG_NO_UI;
    Info.lpFile:=PChar(FileName);
    Info.lpParameters:=PTail;
    Info.nShow:=SW_SHOW;
    IF NOT (Assigned(Event) OR Assigned(EndedFlag)) THEN
      Result:=ShellExecuteEx(@Info)
    ELSE BEGIN
      Info.fMask:=Info.fMask OR SEE_MASK_NOCLOSEPROCESS;
      Result:=ShellExecuteEx(@Info) AND (Info.hProcess>0);
      IF Result THEN
        IF Assigned(Event) THEN
          TWaitThread.Create(FileName,Info.hProcess,Event,Sender)
        ELSE
          TWaitThread.Create(FileName,Info.hProcess,EndedFlag)
    END
  END;

FUNCTION ShellExec(CONST FileName,Tail : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL) : BOOLEAN;
  BEGIN
    Result:=ShellExec(FileName,Tail,Event,Sender,NIL)
  END;

FUNCTION ShellExec(CONST FileName : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL) : BOOLEAN;
  BEGIN
    Result:=ShellExec(FileName,'',Event,Sender)
  END;

FUNCTION ShellExec(CONST FileName,Tail : String ; VAR EndedFlag : BOOLEAN) : BOOLEAN;
  BEGIN
    Result:=ShellExec(FileName,Tail,NIL,NIL,@EndedFlag)
  END;

FUNCTION ShellExec(CONST FileName : String ; VAR EndedFlag : BOOLEAN) : BOOLEAN;
  BEGIN
    Result:=ShellExec(FileName,'',EndedFlag)
  END;

PROCEDURE ShellExecExcept(CONST FileName : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL);
  BEGIN
    IF NOT ShellExec(FileName,Event,Sender) THEN RaiseLastOSError
  END;

PROCEDURE ShellExecExcept(CONST FileName,Tail : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL);
  BEGIN
    IF NOT ShellExec(FileName,Tail,Event,Sender) THEN RaiseLastOSError
  END;

PROCEDURE ShellExecExcept(CONST FileName : String ; VAR EndedFlag : BOOLEAN);
  BEGIN
    IF NOT ShellExec(FileName,EndedFlag) THEN RaiseLastOSError
  END;

PROCEDURE ShellExecExcept(CONST FileName,Tail : String ; VAR EndedFlag : BOOLEAN);
  BEGIN
    IF NOT ShellExec(FileName,Tail,EndedFlag) THEN RaiseLastOSError
  END;

{ TSpawnArgs }

CLASS FUNCTION TSpawnArgs.Create(Act : TSpawnAction ; CONST FN : String) : TSpawnArgs;
  BEGIN
    Result.Initialize(Act,FN)
  END;

PROCEDURE TSpawnArgs.Initialize(Act : TSpawnAction ; CONST FN : String);
  BEGIN
    Action:=Act; FileName:=FN
  END;

END.

按如下方式使用:

USES SpawnFuncs;

ShellExec(ProgramToRun,CommandLineArgs,Event,Sender)

ShellExec(ProgramToRunOrFileToOpen,Event,Sender)

,其中

ProgramToRun = Name of program to run
ProgramToRunOrFileToOpen = Program to run, or file to open (f.ex. a .TXT file)
CommandLineArgs = Command line parameters to pass to the program
Event = The (perhaps anonymous) method to run upon start and termination of program
Sender = The Sender parameter to pass to the method

或者,如果您只是想知道子进程何时终止,则有两个简化版本接受BOOLEAN变量,一旦子程序终止,该变量将设置为TRUE。您不需要先将其设置为FALSE,因为它将自动完成:

ShellExec(ProgramToRun,ChildProcessEnded);

如果您不提供事件处理程序或BOOLEAN变量,则ShellExec过程只是运行/打开给定的文件并且不执行回调。

如果您不提供发件人,则Sender参数将在事件处理程序中未定义。

事件处理程序必须是具有以下签名的方法(匿名或其他方式):

PROCEDURE SpawnEvent(Sender : TObject ; CONST Args : TSpawnArgs);

其中Args包含以下字段:

Action = either saStarted or saEnded
FileName = the name of the file that passed to ShellExec

如果您更喜欢使用SEH(结构化异常处理)而不是错误返回值,则可以使用ShellExecExcept PROCEDURE而不是ShellExec FUNCTION。如果执行请求失败,这些将引发操作系统错误。