Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good quality code example of how to call an application and wait for it to exit

Using: Delphi XE2; Windows 32-bit VCL application

From within my Delphi application, I need to call an application using ShellExecute and wait until it finishes before proceeding.

I see many examples here on SO of ShellExecute with MsgWaitForMultipleObjects but can't know which one is the best because they are mostly doing what is not recommended ie. also using Application.ProcessMessages which is not recommended by many.

I see an answer by NFX here in this post which does not use Application.ProcessMessages, but am not sure if it is correct or optimum, and hence this question.

Would be glad if you could provide a good quality code sample.

TIA for any answers.

like image 259
Steve F Avatar asked Feb 14 '23 05:02

Steve F


2 Answers

ShellExecuteEx() and CreateProcess() both return a HANDLE that you can wait on. The HANDLE is signaled when the spawned process exits.

If you have to pump a message queue while waiting, use MsgWaitForMultipleObjects() to detect when a new message is waiting to be processed. Otherwise, you can use WaitForSingleObject() instead.

NFX's answer does not use Application.ProcessMessages() but it still pumps messages nonetheless, so the root issue remains. If you are doing the waiting in the main thread, you cannot avoid that unless you do not mind presenting an unresponsive UI to your users (or the OS). You could alternatively do the wait in a worker thread instead, then you don't need to pump messages while waiting, and your UI will not be blocked. You can disable your UI or display a status UI while waiting, if you want.

like image 149
Remy Lebeau Avatar answered Apr 09 '23 12:04

Remy Lebeau


I use these functions to execute a child process asynchronously and have it call back when the process terminates. It works by creating a thread that waits until the process terminates and then calls back to the main program thread via the event method given. Beware, that your program continues to run while the child process is running, so you'll need some form of logic to prevent an infinite occurence of spawning child processes.

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.

Use it as follows:

USES SpawnFuncs;

ShellExec(ProgramToRun,CommandLineArgs,Event,Sender)

or

ShellExec(ProgramToRunOrFileToOpen,Event,Sender)

where

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

Or, if you are simply interested in knowing when the child process has terminated, there are two simplified versions that accept a BOOLEAN variable that will be set to TRUE as soon as the child program terminates. You don't need to set it to FALSE first, as it will be done automatically:

ShellExec(ProgramToRun,ChildProcessEnded);

If you don't supply an event handler or BOOLEAN variable, the ShellExec procedure simply runs/opens the file given and performs no callback.

If you don't supply a Sender, the Sender parameter will be undefined in the event handler.

The event handler must be a method (anonymous or otherwise) with the following signature:

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

where Args contains the following fields:

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

If you prefer to use SEH (Structured Exception Handling) instead of error return values, you can use the ShellExecExcept PROCEDUREs instead of the ShellExec FUNCTIONs. These will raise an OS Error in case the execute request failed.

like image 44
HeartWare Avatar answered Apr 09 '23 13:04

HeartWare