Difference between revisions of "Executing External Programs"
(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: | ||
− | |||
// 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(' | + | 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.