Executing External Programs

From Free Pascal wiki
Revision as of 13:52, 25 May 2005 by Vincent (talk | contribs) (Added proclargeout)
Jump to navigationJump to search

Introduction

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

TProcess

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

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

A Simple Example

// 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.

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:

// 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.

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 needs to read. 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.

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.