-1

I want to run 5 different .exe files in a certain order and with arguments. Right now I'm trying to use ShellAPI but it doesn't seem to work. Also I need to execute the next file only after the first one is finished, can I do this with ShellAPI? The code I tried to use is

   procedure TForm1.Button1Click(Sender: TObject);
begin
     ShellExecute(0, 'open',
    'C:\Users\ByfyX1\Dropbox\Exjobb\Senaste Fortran 1ajuli\Release\DIG.exe "C:\Users\ByfyX1\Dropbox\Exjobb\V.1 - kopia\Win32\Debug\Indata1.txt"', nil, nil,
    SW_SHOWNORMAL);
end;

The argument here is the 'Indata1.txt' file. Am I giving the argument wrong here? This is the way that I would write in cmd.exe so that's why I'm going this route.

user3464658
  • 229
  • 1
  • 5
  • 14

3 Answers3

2

ShellExecute returns as soon as the process is created. If you wish to wait for the process to complete, you need to take specific steps to do so.

In any case, ShellExecute is the wrong function to use here. That function, and its infinitely more usable friend ShellExecuteEx are designed to perform a wide range of shell operations on files. You are looking to create processes, for which the API to use is CreateProcess.

When you call CreateProcess, you are returned handles to the new process, and to its main thread. You can then wait on the process handle to become signaled. Once it has become signaled, you can then fire off the next process. And so on and so on.

Something like this:

procedure ExecuteAndWait(Command: string; const WorkingDirectory: string);
var
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
begin
  ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
  StartupInfo.cb := SizeOf(StartupInfo);
  StartupInfo.wShowWindow := SW_HIDE;
  UniqueString(Command);
  Win32Check(CreateProcess(
    nil,
    PChar(cmd),
    nil,
    nil,
    True,
    CREATE_NO_WINDOW or NORMAL_PRIORITY_CLASS,
    nil,
    PChar(WorkingDirectory),
    StartupInfo,
    ProcessInfo
  ));
  try
    Win32Check(WaitForSingleObject(ProcessInfo.hProcess, INFINITE)=WAIT_OBJECT_0);
  finally
    CloseHandle(ProcessInfo.hProcess);
    CloseHandle(ProcessInfo.hThread);
  end;
end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thank you! I'm having problems understanding how to properly use `CreateProcess` tho. I'm trying the code like this `var StartInfo: TStartupInfo; ProcInfo: TProcessInformation; CreateOk: boolean; argument: String; path : String; begin FillChar(StartInfo, SizeOf(TStartupInfo), #0); FillChar(ProcInfo, SizeOf(TProcessInformation), #0); StartInfo.cb := SizeOf(TStartupInfo); argument:='Indata1.txt'; path:='DIG.exe'; CreateProcess(PChar(path),PChar(argument), nil, nil, False, CREATE_NEW_PROCESS_GROUP or NORMAL_PRIORITY_CLASS, nil, PChar(path), StartInfo, ProcInfo);` – user3464658 Jul 14 '14 at 11:48
  • 1
    For instance the accepted answer here (http://stackoverflow.com/questions/17336227/how-can-i-wait-until-an-external-process-has-completed) although I don't like the busy loop and the call to ProcessMessages. But you can write the code as you please, you don't have to copy somebody else's code without understanding it. – David Heffernan Jul 14 '14 at 11:50
  • If I understand correctly, the first argument is the app that's going to be run? In my case, DIG.exe. The second argument is the argument I want to send to the app? And the third from last should be the path for the app? I can't copy another persons code as I don't understand it and I need to be able to manipulate my arguments etc. – user3464658 Jul 14 '14 at 11:57
  • You understand incorrectly. Read the documentation more closely. The code in that Q is fine apart from the busy loop. Use WaitForSingleObject. – David Heffernan Jul 14 '14 at 12:13
1

Just for completeness sake, an alternative approach you could take is to generate a Batch (.bat) file then fire it off with your app and let the CLI do the sequencing. Add a small app that runs last that acts like the classic "touch" app in *nix that creates an empty file in the folder that your app can check to see when it's done.

#the batch file
del imdone.txt  #or do this inside of your program instead
do_first.exe argfile.txt
do_second.exe argfile2.txt
. . .
do_last.exe argfilen.txt
touch imdone.txt

Then your program can do other stuff and periodically poll looking for the existence of imdone.txt file. Once it's found, it knows the processing is complete.

Alternatively, you can use the other way of spawning the batch file process so your program waits until it completes.

I've done this kind of thing a lot. It combines simple programming requirements with equally simple scripting.

David Schwartz
  • 1,756
  • 13
  • 18
  • 1
    +1. Your answer inspired me to dig out my copy of Neil Rubenkin's 1993 book "DOS Batch File Lab Notes", which I recalled being hugely entertaining despite the unpromising subject matter. I'd quite forgotten, though, that it contains an 18-line batch file which solves the Towers of Hanoi puzzle (!). – MartynA Jul 14 '14 at 18:34
0

From this question:

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.

Community
  • 1
  • 1
HeartWare
  • 7,464
  • 2
  • 26
  • 30