Executing External Programs

From Free Pascal wiki
Revision as of 05:33, 31 August 2010 by Chronos (talk | contribs)

Deutsch (de) English (en) español (es) français (fr) italiano (it) 日本語 (ja) Nederlands (nl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN)

Introduction

There are multiple ways to run an external program, but this article will only focus on one: TProcess.

If you always use ShellExecute and/or WinExec in Delphi, then you can begin to use TProcess as an alternative in FPC/Lazarus (this is valid too if you are running Lazarus in Linux, because TProcess is cross-platform).

Note: FPC/Lazarus has support for ShellExecute and/or WinExec, but this support is only in Win32. If your program is cross-platform, then use TProcess, it is the best way!

SysUtils.ExecuteProcess

The simplest way if you don't need pipes or any form of control is to simply use SysUtils.ExecuteProcess('/full/path/to/binary',['arg1','arg2']);

TProcess

You can use TProcess to launch external programs. Some of the benefits of using TProcess are that it is:

  • Platform Independent
  • Capable of reading from stdout and writing to stdin.

Note: TProcess is not a terminal/shell! You cannot directly execute scripts or redirect output using operators like "|", ">", "<", "&" etc. It is possible to obtain the same results with TProcess using pascal, some examples are below..

Important: You must specify the full path to the executable. For example '/bin/cp' instead of 'cp'. If the program is in the standard PATH then you can use the function FindDefaultExecutablePath from the FileUtil unit of the LCL.

A Simple Example

<pascal>

// This is a demo program that shows how to launch
// an external program.
program launchprogram;

// Here we include files that have useful functions
// and procedures we will need.
uses 
  Classes, SysUtils, Process;

// This is defining the var "AProcess" as a variable 
// of the type "TProcess"
var 
  AProcess: TProcess;

// This is where our program starts to run
begin
  // Now we will create the TProcess object, and
  // assign it to the var AProcess.
  AProcess := TProcess.Create(nil);

  // Tell the new AProcess what the command to execute is.
  // Let's use the FreePascal compiler
  AProcess.CommandLine := 'ppc386 -h';

  // We will define an option for when the program
  // is run. This option will make sure that our program
  // does not continue until the program we will launch
  // has stopped running.                vvvvvvvvvvvvvv
  AProcess.Options := AProcess.Options + [poWaitOnExit];

  // Now that AProcess knows what the commandline is 
  // we will run it.
  AProcess.Execute;

  // This is not reached until ppc386 stops running.
  AProcess.Free;   
end.

</pascal>

That's it! You have just learned how to run an external program from inside your own program.

An Improved Example

That's nice, but how do I read the Output of a program that I have run?

Well, let's expand our example a little and do just that:

<delphi>

// This is a demo program that shows how to launch
// an external program and read from it's output.
program launchprogram;

// Here we include files that have useful functions
// and procedures we will need.
uses 
  Classes, SysUtils, Process;

// This is defining the var "AProcess" as a variable 
// of the type "TProcess"
// Also now we are adding a TStringList to store the 
// data read from the programs output.
var 
  AProcess: TProcess;
  AStringList: TStringList;

// This is where our program starts to run
begin
  // Now we will create the TProcess object, and
  // assign it to the var AProcess.
  AProcess := TProcess.Create(nil);

  // Create the TStringList object.
  AStringList := TStringList.Create;

  // Tell the new AProcess what the command to execute is.
  // Let's use the FreePascal compiler
  AProcess.CommandLine := 'ppc386 -h';

  // We will define an option for when the program
  // is run. This option will make sure that our program
  // does not continue until the program we will launch
  // has stopped running. Also now we will tell it that
  // we want to read the output of the file.
  AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];

  // Now that AProcess knows what the commandline is 
  // we will run it.
  AProcess.Execute;
  
  // This is not reached until ppc386 stops running.

  // Now read the output of the program we just ran
  // into the TStringList.
  AStringList.LoadFromStream(AProcess.Output);
  
  // Save the output to a file.
  AStringList.SaveToFile('output.txt');

  // Now that the file is saved we can free the 
  // TStringList and the TProcess.
  AStringList.Free;
  AProcess.Free;   
end.

</delphi>

Reading large output

In the previous example we waited until the program exited. Then we read, what the program has written to its output. But suppose the program writes a lot of data to the output, the pipe becomes full and the called progam waits until the pipe has been read from. But the calling program doesn't read from it, until the called program has ended. A dead lock occurs.

The following example therefore doesn't use poWaitOnExit, but reads from the output, while the program is still running. The output is stored in a memory stream, that can be used later to read the output into a TStringList.

<delphi> program procoutlarge;

{
    Copyright (c) 2004 by Marc Weustink

    This example is creeated in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
}

uses
  Classes, Process, SysUtils;

const
  READ_BYTES = 2048;
  
var
  S: TStringList;
  M: TMemoryStream;
  P: TProcess;
  n: LongInt;
  BytesRead: LongInt;

begin
  // We cannot use poWaitOnExit here since we don't
  // know the size of the output. On Linux the size of the
  // output pipe is 2 kB. If the output data is more, we 
  // need to read the data. This isn't possible since we are 
  // waiting. So we get a deadlock here.
  //
  // A temp Memorystream is used to buffer the output
  
  M := TMemoryStream.Create;
  BytesRead := 0;

  P := TProcess.Create(nil);
  P.CommandLine := 'ppc386 -va bogus.pp';
  P.Options := [poUsePipes];
  WriteLn('-- executing --');
  P.Execute;
  while P.Running do
  begin          
    // make sure we have room
    M.SetSize(BytesRead + READ_BYTES);
    
    // try reading it
    n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
    if n > 0 
    then begin
      Inc(BytesRead, n);
      Write('.')
    end
    else begin     
      // no data, wait 100 ms
      Sleep(100); 
    end;
  end;
  // read last part
  repeat
    // make sure we have room
    M.SetSize(BytesRead + READ_BYTES);
    // try reading it
    n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
    if n > 0 
    then begin
      Inc(BytesRead, n);
      Write('.');
    end;
  until n <= 0;
  if BytesRead > 0 then WriteLn;
  M.SetSize(BytesRead); 
  WriteLn('-- executed --');
  
  S := TStringList.Create;
  S.LoadFromStream(M);
  WriteLn('-- linecount = ', S.Count, ' --');
  for n := 0 to S.Count - 1 do
  begin
    WriteLn('| ', S[n]);
  end;
  WriteLn('-- end --');
  S.Free;
  P.Free;
  M.Free;
end.</delphi>

Using input and output of a TProcess

See processdemo example in the Lazarus-CCR SVN.

Hints on the use of TProcess

If you are creating a cross-platform program, you can change commandline according to the OS, using directives "{$IFDEF}s" and "{$ENDIF}s".

Example: <delphi> {...}

  AProcess:TProcess.Create(nil)
  {$IFDEF WIN32}
  AProcess.CommandLine := 'calc.exe'; //Windows Calc
  {$ENDIF}
  {$IFDEF LINUX}
  AProcess.CommandLine := 'kcalc'; //KDE Calc
  {$ENDIF}
  AProcess.Execute; //in alternative, you can use AProcess.Active:=True
{...}</delphi>

Example of "talking" with aspell process

Inside pasdoc source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:

  • PasDoc_ProcessLineTalk.pas unit implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.
  • PasDoc_Aspell.pas units implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.

Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.

See also