Link to home
Start Free TrialLog in
Avatar of LeTay
LeTay

asked on

Execute an external program from inside a Delphi application

I am looking for a working Delphi code that when it will be executed, will execute an external program, says "myprogram.exe", passing argument "myargument".
But the Delphi application should "wait" until "myprogram.exe" terminates, before to continue execution.
I had some old stuff already, but it does not work anymore.
Delphi is 10.1 Berlin running on Windows 10
Thanks
Avatar of Manuel Lopez-Michelone
Manuel Lopez-Michelone
Flag of Mexico image

Maybe are several ways to do this but I dont know how good are these approaches:

1. Check if a process is active (there must be an API for this)
2. Maybe your external application can do something you can notice from your delphi program. For instance, if a file open was closed. So you can try to open that file. If you get an error, you probably are not done with the external app running. I dont know how good is this specific approach.
3. I was thinking in measure how much time the external app runs. So you can put a timer to expecting that app finished and you resume your delphi program.

I found this code, maybe it can work for you:

// Execute the Windows Calculator and pop up
// a message when the Calc is terminated.
uses ShellApi;
...
var
  SEInfo: TShellExecuteInfo;
  ExitCode: DWORD;
  ExecuteFile, ParamString, StartInString: string;
begin
  ExecuteFile:='c:\Windows\Calc.exe';
 
  FillChar(SEInfo, SizeOf(SEInfo), 0);
  SEInfo.cbSize := SizeOf(TShellExecuteInfo);
  with SEInfo do begin
    fMask := SEE_MASK_NOCLOSEPROCESS;
    Wnd := Application.Handle;
    lpFile := PChar(ExecuteFile);
{
ParamString can contain the
application parameters.
}
// lpParameters := PChar(ParamString);
{
StartInString specifies the
name of the working directory.
If ommited, the current directory is used.
}
//  lpDirectory := PChar(StartInString);
    nShow := SW_SHOWNORMAL;
  end;
  if ShellExecuteEx(@SEInfo) then begin
    repeat
      Application.ProcessMessages;
      GetExitCodeProcess(SEInfo.hProcess, ExitCode);
    until (ExitCode <> STILL_ACTIVE) or
       Application.Terminated;
    ShowMessage('Calculator terminated');
  end
  else ShowMessage('Error starting Calc!');
end;

Also, I found this

An example uses "CreateProcress" (which is more appropriate on a 32bit-platform)  to start notepad and waits until you close it:


var
    StartupInfo: TStartupInfo;
    ProcessInfo: TProcessInformation;
    NotepadExe:  String;
begin
    SetLength(NotepadExe, MAX_PATH);
    GetWindowsDirectory(Pchar(NotepadExe), MAX_PATH);
    StrCat(Pchar(NotepadExe),'\notepad.exe');

    ZeroMemory(@StartupInfo,SizeOf(TStartupInfo));
    StartupInfo.cb := SizeOf(TStartupInfo);

    if(CreateProcess(Pchar(NotepadExe), Nil, Nil, Nil,
                     False, NORMAL_PRIORITY_CLASS,
                     Nil, Nil, StartupInfo,ProcessInfo))    
    then
    begin
        WaitForSingleObject(pi.hProcess,INFINITE);
        ShowMessage('Hi there');
        CloseHandle(ProcessInfo.hProcess);
        CloseHandle(ProcessInfo.hThread);
    end
end;

Note: none of these ideas are mine. I found on the net: https://www.tek-tips.com/viewthread.cfm?qid=186953
ASKER CERTIFIED SOLUTION
Avatar of Ferruccio Accalai
Ferruccio Accalai
Flag of Italy image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of LeTay
LeTay

ASKER

Will try some of these codes and come back to experts once completed...

Avatar of LeTay

ASKER

How can I pass a parameter in the two first example of the notepad ?
I need to pass a file name...
Avatar of LeTay

ASKER

I tried RunAsAdminAndWait passing the notepad exe full file name
I got the Windows warning that I was running as admin, I accepted, then nothing else, no notepad running and back to the caller
To pass a filename to a notepad, you can try this:

begin
  ShellExecute(Handle,'open',
  'c:\windows\notepad.exe',
  'c:\SomeText.txt', nil, SW_SHOWNORMAL) ;
end;

Remember to include shellapi in your uses clause.
I tried RunAsAdminAndWait passing the notepad exe full file name
I got the Windows warning that I was running as admin, I accepted, then nothing else, no notepad running and back to the caller
Ok, here's a simple sample using my function tested on Delphi 10.4
unit unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Winapi.Shellapi, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function RunAsAdminAndWait(hWnd: hWnd; FileName: string; Parameters: string;
  ShowCommand: Integer = SW_SHOWNORMAL): Boolean;
{
  Redesign for UAC Compatibility (UAC)
  http://msdn.microsoft.com/en-us/library/bb756922.aspx
}
var
  sei: TShellExecuteInfo;
  ExitCode: dword;
begin
  ZeroMemory(@sei, SizeOf(sei));
  sei.cbSize := SizeOf(TShellExecuteInfo);
  sei.Wnd := hWnd;
  sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI or
    SEE_MASK_NOCLOSEPROCESS;
  sei.lpVerb := pchar('runas');
  sei.lpFile := pchar(FileName); // PAnsiChar;
  if Parameters <> '' then
    sei.lpParameters := pchar(Parameters); // PAnsiChar;
  sei.nShow := ShowCommand; // Integer;
  if ShellExecuteEx(@sei) then
  begin
    repeat
      Application.ProcessMessages;
      GetExitCodeProcess(sei.hProcess, ExitCode);
    until (ExitCode <> STILL_ACTIVE) or Application.Terminated;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  RunAsAdminAndWait(Application.handle, 'c:\windows\notepad.exe', 'c:\Test.txt',
    SW_SHOWNORMAL);
  showMessage('Notepad closed');
end;

end.

Open in new window


Depending on the idle time your want to give other applications and the responsiveness of your UI, the actual wait should be:

  bOK := Boolean(ShellExecuteEx(@Info));
  if bOK then
  begin
    if bWait then
    begin
      while
        WaitForSingleObject(Info.hProcess, 100) = WAIT_TIMEOUT
        do Application.ProcessMessages;
      bOK := GetExitCodeProcess(Info.hProcess, DWORD(Result));
    end
    else
      Result := 0;
  end;

Open in new window

where WAIT_TIMEOUT is a value between 500ms, especially for animating or updating the wait message and the average external application runtime.

btw, for optimal responsiveness, you would use a thread to run the execute in and then you can do in the UI what you want.