【问题标题】:Lazarus (freepascal) Reading large output from TProcessLazarus (freepascal) 从 TProcess 读取大量输出
【发布时间】:2014-01-07 03:18:00
【问题描述】:

我正在使用 TProcess 和来自thisfreepascal wiki 页面的建议读取 Lazarus 中的大型进程输出数据。

wiki 页面建议创建一个循环来读取流程输出数据,如下所示:

// ... If you want to read output from an external process, this is the code you should adapt for production use.

while True do
  begin          
    MemStream.SetSize(BytesRead + 2024); // make sure we have room
    NumBytes := OurProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
    if NumBytes > 0
    then begin
      Inc(BytesRead, NumBytes);
      Write('.') //Output progress to screen.
    end else 
      BREAK // Program has finished execution.
  end;

// "Then read the MemStream to do your job" 

维基页面还提到调用程序应该从输出管道中读取以防止它被填满。

那么,有多少数据会使输出管道变满?

为什么我们应该在上述循环中使用MemStream (TMemoryStream) 而不是直接从OurProcess.Output 流中读取(使用bytesAvailable 等)?

我正在从一个进程中读取 80MB 的 wav 数据,我注意到 MemStreamOurProcess.Output 流具有相同数量的数据!内存使用量翻倍。因此,wiki 建议的方法不能被认为是有效的或优化的。还是我遗漏了什么?

【问题讨论】:

  • 现在有返回字符串的 runco​​mmand() 宏。

标签: process stream freepascal lazarus


【解决方案1】:

Afaik 输出/输入流是管道的流形式,而不是内存流。您看到的值是从操作系统句柄中检索的,而不是从分配给 FPC 应用本身的内存中检索的。

这就像您可以在不读取整个文件的情况下询问磁盘上文件的 .size。

【讨论】:

  • 那么.output 流在.execute 完成时是否 包含整个数据?以及.output可以保证多少数据?
  • 亲爱的自己,6年后,我告诉你.output中的数据量没有保证。也不能保证剩余数据在.output 中,而.RunningTrue 变为False。您必须仅依赖.Output.Read 和/或.StdErr.Read 的返回值,即如果0 句柄已关闭,则不会再读取数据。然后你可以退出你的循环或者你正在做的任何事情。
  • 在 FPC 3.2.0 中,查看 TProcess.Runcommandloop,它几乎是最好的方法的蓝图。
  • 我指的是MemStream 无论如何都使用Output.Read 复制数据。
【解决方案2】:
    procedure RunExternalAppInMemo(DosApp:String;AMemo:TMemo);

    const READ_BYTES = 2048;
    var
    aProcess: TProcess; //TProcess is crossplatform is best way
    MemStream: TMemoryStream;
    NumBytes: LongInt;
    BytesRead: LongInt;
    Lines: TStringList;
  begin
   // A temp Memorystream is used to buffer the output
   MemStream := TMemoryStream.Create;
   Lines :=TStringList.Create;
   BytesRead := 0;

     aProcess := TProcess.Create(nil);
     aProcess.CommandLine := DosApp;
     aprocess.ShowWindow := swoHIDE;
     AProcess.Options := AProcess.Options + [poUsePipes];

     aProcess.Execute;
     while aProcess.Running do
     begin
       // make sure we have room
       MemStream.SetSize(BytesRead + READ_BYTES);

       // try reading it
       NumBytes := aProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
       if NumBytes > 0 // All read() calls will block, except the final one.
          then Inc(BytesRead, NumBytes)
       else
          BREAK // Program has finished execution.
     end;
     MemStream.SetSize(BytesRead);
     Lines.LoadFromStream(MemStream);
     AMemo.lines.AddStrings(Lines);
     aProcess.Free;
     Lines.Free;
     MemStream.Free;
  end;

【讨论】:

    【解决方案3】:

    我今天正在处理这个问题,我修改了 Georgescu 的答案,因为我希望 Memo 即时显示输出流

    procedure RunExternalAppInMemo(DosApp:String;AMemo:TMemo);
    
        const READ_BYTES = 2048;
        var
        aProcess: TProcess; //TProcess is crossplatform is best way    
        NumBytes: LongInt;
        Buffer: array of byte;    
        
      begin
        // set the size of your buffer
         SetLength(Buffer,READ_BYTES);   
         aProcess := TProcess.Create(nil);
         aProcess.CommandLine := DosApp;
         aprocess.ShowWindow := swoHIDE;
         AProcess.Options := AProcess.Options + [poUsePipes];     
         aProcess.Execute;
         
         while aProcess.Running do
          begin
           // try reading it
           NumBytes := aProcess.Output.Read(Buffer[0], length(buffer)*sizeof(byte)); // I usually do it that way, so I can change Buffer size on if needed
           AProcess.Suspend; //I have no experience with pipes, but it seems way I won loose eny output?
           if NumBytes > 0 then // All read() calls will block, except the final one.
            begin     
                
              AMemo.Lines.Add(Pchar(Buffer);            
              application.ProcessMessages;
              AProcess.Resume; 
            end
           else
              BREAK; // Program has finished execution.         
         end;
         setlength(Buffer,0);
         aProcess.Free;     
      end;
    

    【讨论】:

    • AProcess.Suspend(和Resume)是不需要的。输出存储在 os 缓冲区中,如果它已满,则执行被阻塞,直到另一端从中读取。
    • NumBytes 应该用于使用setString 从缓冲区读取,现在您只需假设buffernull 终止的。最后,我会像 while NumBytes > 0 这样循环,因为不能保证(我认为)在 aProcess.Running 变为 false 时您已经阅读了所有输出。
    • 请不要发布不是问题答案的帖子。
    • 是的,我同意循环,我正在使用不断提供输出的进程,在终止时停止 - 一旦我阅读了您的评论,很明显情况并非总是如此。关于不发帖,抱歉,这可能是个愚蠢的问题,但就在最近,我觉得我至少应该尝试回馈社区,可能应该找到一些“一般发帖规则”或……这不被视为答案,因为我的解决方案是相同的与之前的答案一样,只需添加不属于问题的功能吗?
    猜你喜欢
    • 1970-01-01
    • 2022-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-05
    • 1970-01-01
    相关资源
    最近更新 更多