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
This example requires the TPJPipe class from the PJPipe.pas unit. The unit is included in the I/O Utitlity Classes download.
Most of the previous examples have had to handle events to provide some of the needed functionality. In the later examples they have also had to manipulate pipes. This is all very well, but it does rather clutter up the main form code. There are a couple of alternatives that move the messy code out of the form:
There are benefits to both approaches. In this example we’re going to look only at the latter case and derive a new class from TPJCustomConsoleApp.
Look again at Example 7. In essence what this code does is:
We can replace this with the following:
This has moved all knowledge of how to load streams into pipes and vice versa out of the form. We can create a descendant of TPJCustomConsoleApp that performs the task at item 3 above.
In this example we will recreate Example 7 by subclassing TPJCustomConsoleApp.
Start a new Delphi GUI project and add a new unit file, named UConsoleAppEx.pas
. Add the following code to its interface section:
uses
Classes, PJConsoleApp, PJPipe;
type
TConsoleAppEx = classTPJCustomConsoleApp-
private
fOutPipe: TPJPipe;
fOutStream: TStream;
protected
procedure DoWork; override;
public
function Execute(const CmdLine: string; const InStream,
OutStream: TStream): Boolean;
// Make protected properties public
property ErrorCode;
property ErrorMessage;
property TimeSlice;
end;
The Execute method replaces the one in the base class. It receives the command we are to execute as well as an input stream containing data to be piped to the console process’ standard input and a stream to receive data piped from standard output. We also make three required properties public. (All properties of TPJCustomConsoleApp are protected.) The inherited DoWork method is overridden to read the output pipe. This replaces the OnWork event handler in Example 7.
TConsoleAppEx is implemented as follows:
uses
SysUtils;
procedure TConsoleAppEx.DoWork;
begin
fOutPipe.CopyToStream(fOutStream, 0);
end;
function TConsoleAppEx.Execute(const CmdLine: string; const InStream,
OutStream: TStream): Boolean;
begin
fOutStream := OutStream;
InPipe := nil;
fOutPipe := nil;
try
// Set up input pipe and associated with console app stdin
InPipe := TPJPipe.Create(InStream.Size);
InPipe.CopyFromStream(InStream, 0);
InPipe.CloseWriteHandle;
// Set up output pipe and associate with console app stdout
fOutPipe := TPJPipe.Create;
StdIn := InPipe.ReadHandle;
StdOut := fOutPipe.WriteHandle;
// Run the application
Result := inherited Execute(CmdLine);
finally
StdIn := 0;
StdOut := 0;
FreeAndNil(fOutPipe);
FreeAndNil(InPipe);
end;
end;
DoWork is straightforward - it copies the current pipe contents to the output stream.
The new Execute method now contains some of the set up code that was in the main form code in Example 7.
First we record a reference to the output stream for later use. Next we create the pipe associated with standard input and copy all the data from InStream to it. The output pipe is then created before associating pipe handles with the console application’s standard input and output. Finally, the Execute method of the base class is called to execute the console application.
Now switch back to the main form. Add UConsoleAppEx to the uses statement. Now drop a button and two memos on the form then create an OnClick event handler for the button, with the following code:
procedure TForm1.Button1Click(Sender: TObject);
var
App: TConsoleAppEx;
InStream: TStream;
OutStream: TStream;
begin
// Set up i/o streams
InStream := nil;
OutStream := nil;
try
InStream := TMemoryStream.Create;
Memo1.Lines.SaveToStream(InStream);
InStream.Position := 0;
OutStream := TMemoryStream.Create;
// Run console app
App := TConsoleAppEx.Create;
try
App.TimeSlice := 2;
if not App.Execute('Echoer "-->"', InStream, OutStream) then
raise Exception.CreateFmt(
'Error %X: %s', [App.ErrorCode, App.ErrorMessage]
);
finally
App.Free;
end;
// Read output stream
OutStream.Position := 0;
Memo2.Lines.LoadFromStream(OutStream);
finally
InStream.Free;
OutStream.Free;
end;
end;
First a stream is created and the contents of Memo1 are copied into it. Next the empty output stream is created. Then we run the console application using our new class. And finally we display the data from the output stream in Memo2.
In the above code
Echoer.exe
from Appendix 2 has been used once again.
Run the application. It should perform exactly the same as Example 7.