swift99
asked on
How to Redirect stdout/stderr from a spawned process to application?
I'm trying to encapsulate a call to create a shell process and capture its stdout/stderr from named pipes for further processing.
I can capture the stdout from a separately started process, but not one spawned from within the application. I suspect (very strongly) that I am missing a flag or setting for CreateProcess that would allow this. That is, if I start the process from a command line with "ipconfig > \\.\pipe\{whatever-the-id- is}" I get the stdout when I read the stream. However, if I start the process with CreateProcess I do not.
Here's the encapsulation code:
unit ShellEncapsulation;
interface
uses Classes, Sysutils, Windows, ShellAPI;
type
TShell = class (TObject)
private
FExecResults: Boolean;
FStdErrName: String;
FCommandLine: String;
FStdOutName: String;
FStderr: TStream;
FStdout: TStream;
hStdErr: THandle;
hStdOut: THandle;
Fbuffersize: Integer;
FTimeOut: Integer;
procedure SetCommandLine(const Value: String);
procedure SetExecResults(const Value: Boolean);
procedure SetStderr(const Value: TStream);
procedure SetStdErrName(const Value: String);
procedure SetStdout(const Value: TStream);
procedure SetStdOutName(const Value: String);
procedure Setbuffersize(const Value: Integer);
procedure SetTimeOut(const Value: Integer);
protected
function GetUniqueName: String;
property CommandLine: String read FCommandLine write SetCommandLine;
property StdErrName: String read FStdErrName write SetStdErrName;
property StdOutName: String read FStdOutName write SetStdOutName;
public
Constructor Create;
Destructor Done;
function Exec (_Commandline: String): Boolean;
property Stdout: TStream read FStdout write SetStdout;
property Stderr: TStream read FStderr write SetStderr;
property ExecResults: Boolean read FExecResults write SetExecResults;
property buffersize: Integer read Fbuffersize write Setbuffersize;
property TimeOut: Integer read FTimeOut write SetTimeOut;
end;
implementation
{ TShell }
constructor TShell.Create;
begin
buffersize := 1024 * 1024; // Allow 1MB Buffer
Timeout := 10000; // Allow 10 seconds time out
end;
destructor TShell.Done;
begin
// StdOut.Close;
StdOut.Free;
StdOut := NIL;
DisconnectNamedPipe (hStdout);
// DeleteFile (StdOutName);
// StdErr.Close
StdErr.Free;
StdErr := Nil;
DisconnectNamedPipe (hStderr);
// DeleteFile (StdErrName);
end;
function TShell.Exec(_Commandline: String): Boolean;
var
StartUpInfo : TStartUpInfo;
ProcessInfo : TProcessInformation;
cmdLine: String;
begin
stdoutname := GetUniquename;
stderrname := GetUniquename;
hStdOut := CreateNamedPipe (PChar(stdOutName),
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE+PIPE_WAIT,
99,
bufferSize,
bufferSize,
TimeOut,
NIL);
hStdErr := CreateNamedPipe (PChar(stdErrName),
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE+PIPE_WAIT,
99,
bufferSize,
bufferSize,
TimeOut,
NIL);
StdOut := THandleStream.Create (hStdOut);
StdErr := THandleStream.Create (hStdErr);
FillChar(StartUpInfo, SizeOf(TStartUpInfo), 0);
with StartUpInfo do begin
cb := SizeOf(TStartUpInfo);
dwFlags := STARTF_USESTDHANDLES;
wShowWindow := SW_SHOWDEFAULT;
hStdOutput := hStdOut;
hStdError := hStdErr;
end; // with
ExecResults := CreateProcess(nil,
PChar(_CommandLine),
nil,
nil,
false,
DETACHED_PROCESS + NORMAL_PRIORITY_CLASS,
nil,
PChar(ExtractFilePath(para mStr(0))),
StartUpInfo,
ProcessInfo);
Result := ExecResults;
end;
function TShell.GetUniqueName: String;
var
aGUID: TGUID;
begin
CreateGUID (aGUID);
// result := '\\.\PIPE\'+Copy (GUIDToString (aGUID),2,8);
result := '\\.\PIPE\'+GUIDToString (aGUID);
end;
procedure TShell.Setbuffersize(const Value: Integer);
begin
Fbuffersize := Value;
end;
procedure TShell.SetCommandLine(cons t Value: String);
begin
FCommandLine := Value;
end;
procedure TShell.SetExecResults(cons t Value: Boolean);
begin
FExecResults := Value;
end;
procedure TShell.SetStderr(const Value: TStream);
begin
FStderr := Value;
end;
procedure TShell.SetStdErrName(const Value: String);
begin
FStdErrName := Value;
end;
procedure TShell.SetStdout(const Value: TStream);
begin
FStdout := Value;
end;
procedure TShell.SetStdOutName(const Value: String);
begin
FStdOutName := Value;
end;
procedure TShell.SetTimeOut(const Value: Integer);
begin
FTimeOut := Value;
end;
end.
I can capture the stdout from a separately started process, but not one spawned from within the application. I suspect (very strongly) that I am missing a flag or setting for CreateProcess that would allow this. That is, if I start the process from a command line with "ipconfig > \\.\pipe\{whatever-the-id-
Here's the encapsulation code:
unit ShellEncapsulation;
interface
uses Classes, Sysutils, Windows, ShellAPI;
type
TShell = class (TObject)
private
FExecResults: Boolean;
FStdErrName: String;
FCommandLine: String;
FStdOutName: String;
FStderr: TStream;
FStdout: TStream;
hStdErr: THandle;
hStdOut: THandle;
Fbuffersize: Integer;
FTimeOut: Integer;
procedure SetCommandLine(const Value: String);
procedure SetExecResults(const Value: Boolean);
procedure SetStderr(const Value: TStream);
procedure SetStdErrName(const Value: String);
procedure SetStdout(const Value: TStream);
procedure SetStdOutName(const Value: String);
procedure Setbuffersize(const Value: Integer);
procedure SetTimeOut(const Value: Integer);
protected
function GetUniqueName: String;
property CommandLine: String read FCommandLine write SetCommandLine;
property StdErrName: String read FStdErrName write SetStdErrName;
property StdOutName: String read FStdOutName write SetStdOutName;
public
Constructor Create;
Destructor Done;
function Exec (_Commandline: String): Boolean;
property Stdout: TStream read FStdout write SetStdout;
property Stderr: TStream read FStderr write SetStderr;
property ExecResults: Boolean read FExecResults write SetExecResults;
property buffersize: Integer read Fbuffersize write Setbuffersize;
property TimeOut: Integer read FTimeOut write SetTimeOut;
end;
implementation
{ TShell }
constructor TShell.Create;
begin
buffersize := 1024 * 1024; // Allow 1MB Buffer
Timeout := 10000; // Allow 10 seconds time out
end;
destructor TShell.Done;
begin
// StdOut.Close;
StdOut.Free;
StdOut := NIL;
DisconnectNamedPipe (hStdout);
// DeleteFile (StdOutName);
// StdErr.Close
StdErr.Free;
StdErr := Nil;
DisconnectNamedPipe (hStderr);
// DeleteFile (StdErrName);
end;
function TShell.Exec(_Commandline: String): Boolean;
var
StartUpInfo : TStartUpInfo;
ProcessInfo : TProcessInformation;
cmdLine: String;
begin
stdoutname := GetUniquename;
stderrname := GetUniquename;
hStdOut := CreateNamedPipe (PChar(stdOutName),
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE+PIPE_WAIT,
99,
bufferSize,
bufferSize,
TimeOut,
NIL);
hStdErr := CreateNamedPipe (PChar(stdErrName),
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE+PIPE_WAIT,
99,
bufferSize,
bufferSize,
TimeOut,
NIL);
StdOut := THandleStream.Create (hStdOut);
StdErr := THandleStream.Create (hStdErr);
FillChar(StartUpInfo, SizeOf(TStartUpInfo), 0);
with StartUpInfo do begin
cb := SizeOf(TStartUpInfo);
dwFlags := STARTF_USESTDHANDLES;
wShowWindow := SW_SHOWDEFAULT;
hStdOutput := hStdOut;
hStdError := hStdErr;
end; // with
ExecResults := CreateProcess(nil,
PChar(_CommandLine),
nil,
nil,
false,
DETACHED_PROCESS + NORMAL_PRIORITY_CLASS,
nil,
PChar(ExtractFilePath(para
StartUpInfo,
ProcessInfo);
Result := ExecResults;
end;
function TShell.GetUniqueName: String;
var
aGUID: TGUID;
begin
CreateGUID (aGUID);
// result := '\\.\PIPE\'+Copy (GUIDToString (aGUID),2,8);
result := '\\.\PIPE\'+GUIDToString (aGUID);
end;
procedure TShell.Setbuffersize(const
begin
Fbuffersize := Value;
end;
procedure TShell.SetCommandLine(cons
begin
FCommandLine := Value;
end;
procedure TShell.SetExecResults(cons
begin
FExecResults := Value;
end;
procedure TShell.SetStderr(const Value: TStream);
begin
FStderr := Value;
end;
procedure TShell.SetStdErrName(const
begin
FStdErrName := Value;
end;
procedure TShell.SetStdout(const Value: TStream);
begin
FStdout := Value;
end;
procedure TShell.SetStdOutName(const
begin
FStdOutName := Value;
end;
procedure TShell.SetTimeOut(const Value: Integer);
begin
FTimeOut := Value;
end;
end.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
thanks for the points :)
ASKER
The actual solution was the sort I expected (duh - you need separate input and and output handles), but it was a sure challenge to sort it out.
Points increased to 150 ... thanks for the assist!