This is a new site that's currently running on alpha code. There are going to be bugs. If you discover any, please report them on the site's issues page (GitHub account required). Thanks.
Warning: Many URLs are going to change. Refer to the README file to discover which library project's documentation has been completed.
Applies to: ~>3.0
You may have seen applications (Inno Setup springs to mind), that display output from a console application in real time in a GUI control. In this example we’ll see how to use TPJConsoleApp to do this.
Here is an overview of what we will do:
Use a console application that processes text on standard input and writes processed output to standard output. We will redirect standard input to a lengthy text file.
We will use
Echoer.exe
from Appendix 2 once more. Again you can use another application provided it processes ANSI text from standard input and writes ANSI text to standard output.
Display the console applications’ standard output in a memo control. To do this we will redirect standard output to a pipe and read the pipe in an OnWork event, passing output to a helper class that updates the memo control.
Do the same thing with the program’s standard error output, displaying that in another memo control.
Start a new Delphi GUI application and drop a button and two memo controls. We will display standard output in Memo1 and standard error in Memo2.
We will use the following helper classes to avoid a considerable amount of complexity in the code:
All these classes are included in the I/O Utitlity Classes download.
First add the PJConsoleApp, PJPipe, PJFileHandle and PJPipeFilters units to the uses statement.
Now add the following declarations to the form class’ private section:
private
fErrFilter, fOutFilter: TPJAnsiSBCSPipeFilter;
procedure ErrLineEndHandler(Sender: TObject; const Line: AnsiString);
procedure OutLineEndHandler(Sender: TObject; const Line: AnsiString);
procedure WorkHandler(Sender: TObject);
procedure CompletionHandler(Sender: TObject);
Create an OnClick event handler for the button as follows:
procedure TForm1.Button1Click(Sender: TObject);
var
App: TPJConsoleApp;
InFile: TPJFileHandle;
OutPipe, ErrPipe: TPJPipe;
const
InFileName = 'InputFile.txt';
begin
fOutFilter := nil;
fErrFilter := nil;
OutPipe := nil;
ErrPipe := nil;
InFile := nil;
try
// Open input file
InFile := TPJFileHandle.Create(InFileName, fmOpenRead or fmShareDenyNone);
// Create output pipes: one each for stdout and stderr
OutPipe := TPJPipe.Create;
ErrPipe := TPJPipe.Create;
// Create filter objects used to format text from output pipe into lines
fOutFilter := TPJAnsiSBCSPipeFilter.Create(OutPipe);
fOutFilter.OnLineEnd := OutLineEndHandler;
fErrFilter := TPJAnsiSBCSPipeFilter.Create(ErrPipe);
fErrFilter.OnLineEnd := ErrLineEndHandler;
App := TPJConsoleApp.Create;
try
// redirect stdin to file and stdout/stderr to pipes
App.StdIn := InFile.Handle;
App.StdOut := OutPipe.WriteHandle;
App.StdErr := ErrPipe.WriteHandle;
App.OnWork := WorkHandler;
App.OnComplete := CompletionHandler;
App.TimeSlice := 1;
if not App.Execute('Echoer') then
raise Exception.CreateFmt(
'Error %X: %s', [App.ErrorCode, App.ErrorMessage]
);
finally
App.Free;
end;
finally
FreeAndNil(fErrFilter);
FreeAndNil(fOutFilter);
ErrPipe.Free;
OutPipe.Free;
InFile.Free;
end;
end;
Here’s what the code does:
InputFile.txt
with some suitably long ANSI text file.The OnWork and OnComplete handlers are where we process the piped output from the console application. Implement these like this:
procedure TForm1.WorkHandler(Sender: TObject);
begin
fOutFilter.ReadPipe;
fErrFilter.ReadPipe;
Application.ProcessMessages; // Let the memo controls update
end;
procedure TForm1.CompletionHandler(Sender: TObject);
begin
fOutFilter.Flush;
fErrFilter.Flush;
end;
WorkHandler calls the ReadPipe methods of the two TPJAnsiSBCSPipeFilter objects. This method simpy copies all available data from the associated pipe and processes it. Importantly, WorkHandler also calls Application.ProcessMessages to enable the memo controls to update.
CompletionHandler causes the TPJAnsiSBCSPipeFilter objects to flush any remaining text from their internal buffers, ensuring it gets written to the memo controls.
So far we haven’t seen how the memo controls actually get updated. This is done in the final piece of code, and that’s the implementation of the TPJAnsiSBCSPipeFilter objects’ OnLineEnd event handlers:
procedure TForm1.OutLineEndHandler(Sender: TObject; const Line: AnsiString);
begin
Memo1.Lines.Add(string(Line));
end;
procedure TForm1.ErrLineEndHandler(Sender: TObject; const Line: AnsiString);
begin
Memo2.Lines.Add(string(Line));
end;
These events are triggered once for each complete line of text that the TPJAnsiSBCSPipeFilter read from the pipes. The events are also triggered for any remaining text when the application ends (in the calls to the Flush method). Each event is handled simply by adding the line of text to the appropriate memo control.
Run the application and watch the output appear in the two memo controls.
The code above is longer than it needs to be in order to make it clear what exactly is happening in the hope that this makes the code easier to understand.
TPJAnsiSBCSPipeFilter is able to take ownership of any pipe passed to its constructor so that the pipe is freed when the filter object is freed. The object also exposes a Pipe property to provide access to the wrapped pipe. This means we don’t need the OutPipe and ErrPipe local variables.
The Button1Click event handler can be shortened to:
procedure TForm1.Button1Click(Sender: TObject);
var
App: TPJConsoleApp;
InFile: TPJFileHandle;
const
InFileName = 'InputFile.txt';
begin
fOutFilter := nil;
fErrFilter := nil;
InFile := nil;
try
InFile := TPJFileHandle.Create(InFileName, fmOpenRead or fmShareDenyNone);
fOutFilter := TPJAnsiSBCSPipeFilter.Create(TPJPipe.Create, True);
fOutFilter.OnLineEnd := OutLineEndHandler;
fErrFilter := TPJAnsiSBCSPipeFilter.Create(TPJPipe.Create, True);
fErrFilter.OnLineEnd := ErrLineEndHandler;
App := TPJConsoleApp.Create;
try
App.StdIn := InFile.Handle;
App.StdOut := fOutFilter.Pipe.WriteHandle;
App.StdErr := fErrFilter.Pipe.WriteHandle;
App.OnWork := WorkHandler;
App.OnComplete := CompletionHandler;
App.TimeSlice := 1;
if not App.Execute('Echoer') then
raise Exception.CreateFmt(
'Error %X: %s', [App.ErrorCode, App.ErrorMessage]
);
finally
App.Free;
end;
finally
FreeAndNil(fErrFilter); // frees pipe object and closes pipe handles
FreeAndNil(fOutFilter); // frees pipe object and closes pipe handles
InFile.Free;
end;
end;
The pipe objects are created on the fly in the TPJAnsiSBCSPipeFilter constructor calls. The optional second True
parameter tells the filter object to free the pipe when it is destroyed. We use the Pipe property of the filter to access the pipe’s WriteHandle when setting the console application object’s StdOut and StdIn properties.
The is one further bit of slimming down we can do, and that is to remove the console application object’s OnComplete event handler. We can do this because TPJAnsiSBCSPipeFilter instance automatically calls the Flush method when the object is being destroyed. This causes the OnLineEnd event handler to be called with any remaining text. Therefore the explicit call to Flush in the OnComplete handler is redundant.