如何确定StdOut和StdErr的正确顺序/缓冲区

时间:2013-04-20 17:53:42

标签: delphi console console-application pipe

我有一个外部控制台应用程序我想将它的完整输出(StdOut和StdError)实时捕获到备忘录中(就像我会双击它一样)。

使用屏幕截图关于应用程序的信息: Delphi Console pipes switched?

我写了一个单位来阅读管道:

unit uConsoleOutput;
interface

uses  Classes,
      StdCtrls,
      SysUtils,
      Messages,
      Windows;

  type
  ConsoleThread = class(TThread)
  private
    OutputString : String;
    procedure SetOutput;
  protected
    procedure Execute; override;
  public
    App           : WideString;
    Memo          : TMemo;
    Directory     : WideString;
  end;

  type
    PConsoleData = ^ConsoleData;
    ConsoleData = record
    OutputMemo          : TMemo;
    OutputApp           : WideString;
    OutputDirectory     : WideString;
    OutputThreadHandle  : ConsoleThread;
  end;

function StartConsoleOutput (App : WideString; Directory : WideString; Memo : TMemo) : PConsoleData;
procedure StopConsoleOutput  (Data : PConsoleData);

implementation

procedure ConsoleThread.SetOutput;
begin
  Memo.Lines.BeginUpdate;
  Memo.Text := Memo.Text + OutputString;
  Memo.Lines.EndUpdate;
end;

procedure ConsoleThread.Execute;
const
  ReadBuffer = 50;
var
  Security    : TSecurityAttributes;
  InputPipeRead,
  InputPipeWrite,
  OutputPipeRead,
  OutputPipeWrite,
  ErrorPipeRead,
  ErrorPipeWrite : THandle;
  start       : TStartUpInfo;
  ProcessInfo : TProcessInformation;
  Buffer      : Pchar;
  BytesRead   : DWord;
  Apprunning  : DWord;
begin
  Security.nlength := SizeOf(TSecurityAttributes) ;
  Security.lpsecuritydescriptor := nil;
  Security.binherithandle := true;
  if Createpipe (InputPipeRead, InputPipeWrite, @Security, 0) then begin
    if CreatePipe(OutputPipeRead, OutputPipeWrite, @Security, 0) then begin
      if CreatePipe(ErrorPipeRead, ErrorPipeWrite, @Security, 0) then begin
        Buffer := AllocMem(ReadBuffer + 1) ;
        FillChar(Start,Sizeof(Start),#0) ;
        start.cb := SizeOf(start) ;
        start.hStdOutput  := OutputPipeWrite;
        start.hStdError   := ErrorPipeWrite;
        start.hStdInput   := InputPipeRead;
        start.dwFlags     := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
        start.wShowWindow := SW_HIDE;
        if CreateProcessW(nil,pwidechar(APP),@Security,@Security,true,NORMAL_PRIORITY_CLASS,nil,pwidechar(Directory),start,ProcessInfo) then begin
          while not(terminated) do begin

          // ====> Stuck here.
            // ReadErrorPipe
            BytesRead := 0;
            if Terminated then break;
            ReadFile(ErrorPipeRead,Buffer[0], ReadBuffer,BytesRead,nil);
            if Terminated then break;
            Buffer[BytesRead]:= #0;
            if Terminated then break;
            //OemToAnsi(Buffer,Buffer);
            if Terminated then break;
            OutputString := Buffer;
            if Terminated then break;
            Synchronize(SetOutput);

            // ReadStdOut
            BytesRead := 0;
            if Terminated then break;
            ReadFile(OutputPipeRead,Buffer[0], ReadBuffer,BytesRead,nil);
            if Terminated then break;
            Buffer[BytesRead]:= #0;
            if Terminated then break;
            //OemToAnsi(Buffer,Buffer);
            if Terminated then break;
            OutputString := Buffer;
            if Terminated then break;
            Synchronize(SetOutput);

            end;
          FreeMem(Buffer);
          CloseHandle(ProcessInfo.hProcess);
          CloseHandle(ProcessInfo.hThread);
          CloseHandle(InputPipeRead);
          CloseHandle(InputPipeWrite);
          CloseHandle(OutputPipeRead);
          CloseHandle(OutputPipeWrite);
          CloseHandle(ErrorPipeRead);
          CloseHandle(ErrorPipeWrite);
        end;
      end;
    end;
  end;
end;

function StartConsoleOutput (App : WideString; Directory : WideString; Memo : TMemo) : PConsoleData;
begin
  result                          := VirtualAlloc(NIL, SizeOf(ConsoleData), MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  Memo.DoubleBuffered             := TRUE;
  with PConsoleData(result)^ do begin
    OutputMemo                          := Memo;
    OutputApp                           := App;
    OutputDirectory                     := Directory;
    OutputThreadHandle                  := ConsoleThread.Create(TRUE);
    OutputThreadHandle.FreeOnTerminate  := TRUE;
    OutputThreadHandle.Memo             := Memo;
    OutputThreadHandle.App              := App;
    OutputThreadHandle.Directory        := Directory;
    OutputThreadHandle.Resume;
  end;
end;

procedure StopConsoleOutput  (Data : PConsoleData);
begin
  with PConsoleData(Data)^ do begin
    OutputThreadHandle.Terminate;
    while not(OutputThreadHandle.Terminated) do sleep (100);
  end;
  VirtualFree (Data,0, MEM_RELEASE);
end;

end.

我启动这样的应用程序:

StartConsoleOutput ('C:\myexternalapp.exe', 'C:\', Memo1);

我想首先输出stderror然后输出stdoutput(或者至少以1:1的顺序输出控制台输出的内容) 问题是顺序和适当的缓冲区。

如何以正确的顺序读取这2个管道以及使用哪个缓冲区大小来实现1:1的输出?

1 个答案:

答案 0 :(得分:3)

使用PeekNamedPipe()确定管道何时可以读取数据以及可以读取多少字节。尽管它的名字,它适用于命名管道和匿名管道。