Link to home
Start Free TrialLog in
Avatar of swift99
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(paramStr(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(const Value: String);
begin
  FCommandLine := Value;
end;

procedure TShell.SetExecResults(const 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.
ASKER CERTIFIED SOLUTION
Avatar of DragonSlayer
DragonSlayer
Flag of Malaysia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of swift99
swift99

ASKER

I also achieved a similar solution at about 1:00 AM.  My clue was that I went home where I run Win98,which doesn't support named pipes, so I used anonymous pipes instead.  When you create an anonymous pipe, both the read and write handle are returned.  I passed the write handle to CreateProcess, and opened a streeam on the read handle, and voila!

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!
thanks for the points :)