【问题标题】:How to determine the proper order/buffer for StdOut and StdError如何确定 StdOut 和 StdErr 的正确顺序/缓冲区
【发布时间】:2013-04-13 22:58:06
【问题描述】:

我有一个外部控制台应用程序,我想将其完整输出(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);

我想先输出标准错误,然后输出标准输出(或者至少按照控制台输出的 1:1 顺序输出) 问题在于顺序和适当的缓冲区大小。

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

【问题讨论】:

  • 为什么不把 stderr 和 stdout 都写到同一个管道?
  • 我试过了,但好像少了点什么。那会是 hstdouterror := OutputPipeWrite 吗?
  • 是的。两个管道而不是三个。无论你分配给 stdout,你都分配给 stderr。抱歉帮不上忙,出去吧。
  • 我试过了,但我猜是应用程序搞砸了……我无法捕捉到它的所有输出。我什至使用了 buffersize 1。
  • 如果将 stdout 和 stderr 都映射到同一个管道,则不会发生切换。我认为这是确保信息按照发送顺序到达的唯一方法。我无法理解您使用 VirtualAlloc。您可以在线程类中声明您的记录类型的变量。

标签: delphi console console-application pipe


【解决方案1】:

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

【讨论】:

  • 这样就可以了!但是你需要添加睡眠,否则如果你总是检查 PeekNamedPipe,它会消耗大量的 CPU 资源。
猜你喜欢
  • 1970-01-01
  • 2013-09-02
  • 1970-01-01
  • 2014-07-29
  • 2011-08-02
  • 1970-01-01
  • 2020-09-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多