我正在使用 TProcess 和这个 freepascal 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"
wiki 页面还提到调用程序应从输出管道读取以防止其变满。
那么,有多少数据使输出管道充满?
为什么我们应该使用MemStream
(TMemoryStream)而不是直接从上述循环中的OurProcess.Output
流中读取(使用bytesAvailable
等)?
我正在从一个进程中读取 80MB 的 wav 数据,我注意到 MemStream
和OurProcess.Output
流的数据量相同!内存使用量翻倍。因此,来自 wiki 的建议方法不能被认为是有效或优化的。还是我错过了什么?
Afaik 输出/输入流是管道的流形式,而不是内存流。你看到的值是从操作系统句柄检索的,而不是从分配给 FPC 应用本身的内存中检索的。
这就像您可以在不读取整个文件的情况下请求磁盘上文件的 .size 一样。
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;
我今天正在处理这个问题,我已经修改了 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;