Difference between revisions of "Executing External Programs"

From Free Pascal wiki
Jump to navigationJump to search
(fixed improved example)
(Added proclargeout)
Line 52: Line 52:
  
  
 +
===An Improved Example===
 
That's nice, but how do I read the Output of a program that I have run?
 
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:
 
Well, let's expand our example a little and do just that:
===An Improved Example===
 
  
 
  // This is a demo program that shows how to launch
 
  // This is a demo program that shows how to launch
Line 105: Line 105:
 
    
 
    
 
   // Save the output to a file.
 
   // Save the output to a file.
   AStringList.SaveToFile('c:\output.txt');
+
   AStringList.SaveToFile('output.txt');
 
   
 
   
 
   // Now that the file is saved we can free the  
 
   // Now that the file is saved we can free the  
Line 111: Line 111:
 
   AStringList.Free;
 
   AStringList.Free;
 
   AProcess.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.
 
  end.

Revision as of 13:52, 25 May 2005

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.