Delphi 从多个源捕获彩色控制台输出
我编写了一个控制台应用程序,可以在命令行上并行执行多个命令。Delphi 从多个源捕获彩色控制台输出,delphi,process,output,console-application,Delphi,Process,Output,Console Application,我编写了一个控制台应用程序,可以在命令行上并行执行多个命令。 我这样做主要是出于兴趣,因为我正在从事的软件项目的构建过程过度使用了命令行 目前,在工作线程中创建子进程之前,我会创建一个匿名管道,以捕获子进程在其生命周期内创建的所有输出。 子进程终止后,工作线程将捕获的内容推送到等待的主进程,然后主进程将其打印出来 以下是我的创建和捕获过程: procedure ReadPipe(const ReadHandle: THandle; const Output: TStream);
我这样做主要是出于兴趣,因为我正在从事的软件项目的构建过程过度使用了命令行 目前,在工作线程中创建子进程之前,我会创建一个匿名管道,以捕获子进程在其生命周期内创建的所有输出。
子进程终止后,工作线程将捕获的内容推送到等待的主进程,然后主进程将其打印出来 以下是我的创建和捕获过程:
procedure ReadPipe(const ReadHandle: THandle; const Output: TStream);
var
Buffer: TMemoryStream;
BytesRead, BytesToRead: DWord;
begin
Buffer := TMemoryStream.Create;
try
BytesRead := 0;
BytesToRead := 0;
if PeekNamedPipe(ReadHandle, nil, 0, nil, @BytesToRead, nil) then
begin
if BytesToRead > 0 then
begin
Buffer.Size := BytesToRead;
ReadFile(ReadHandle, Buffer.Memory^, Buffer.Size, BytesRead, nil);
if Buffer.Size <> BytesRead then
begin
Buffer.Size := BytesRead;
end;
if Buffer.Size > 0 then
begin
Output.Size := Output.Size + Buffer.Size;
Output.WriteBuffer(Buffer.Memory^, Buffer.Size);
end;
end;
end;
finally
Buffer.Free;
end;
end;
function CreateProcessWithRedirectedOutput(const AppName, CMD, DefaultDir: PChar; out CapturedOutput: String): Cardinal;
const
TIMEOUT_UNTIL_NEXT_PIPEREAD = 100;
var
SecurityAttributes: TSecurityAttributes;
ReadHandle, WriteHandle: THandle;
StartupInfo: TStartupInfo;
ProcessInformation: TProcessInformation;
ProcessStatus: Cardinal;
Output: TStringStream;
begin
Result := 0;
CapturedOutput := '';
Output := TStringStream.Create;
try
SecurityAttributes.nLength := SizeOf(SecurityAttributes);
SecurityAttributes.lpSecurityDescriptor := nil;
SecurityAttributes.bInheritHandle := True;
if CreatePipe(ReadHandle, WriteHandle, @SecurityAttributes, 0) then
begin
try
FillChar(StartupInfo, Sizeof(StartupInfo), 0);
StartupInfo.cb := SizeOf(StartupInfo);
StartupInfo.hStdOutput := WriteHandle;
StartupInfo.hStdError := WriteHandle;
StartupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
StartupInfo.dwFlags := STARTF_USESTDHANDLES;
if CreateProcess(AppName, CMD,
@SecurityAttributes, @SecurityAttributes,
True, NORMAL_PRIORITY_CLASS,
nil, DefaultDir,
StartupInfo, ProcessInformation)
then
begin
try
repeat
ProcessStatus := WaitForSingleObject(ProcessInformation.hProcess, TIMEOUT_UNTIL_NEXT_PIPEREAD);
ReadPipe(ReadHandle, Output);
until ProcessStatus <> WAIT_TIMEOUT;
if not Windows.GetExitCodeProcess(ProcessInformation.hProcess, Result) then
begin
Result := GetLastError;
end;
finally
Windows.CloseHandle(ProcessInformation.hProcess);
Windows.CloseHandle(ProcessInformation.hThread);
end;
end
else
begin
Result := GetLastError;
end;
finally
Windows.CloseHandle(ReadHandle);
Windows.CloseHandle(WriteHandle);
end;
end
else
begin
Result := GetLastError;
end;
CapturedOutput := Output.DataString;
finally
Output.Free;
end;
end;
我也阅读了和,和,但对于我的用例,我不能完全理解它
保存/接收子进程控制台输出的颜色信息的正确/最佳方法是什么?我使用并为每个线程提供其自己的控制台屏幕缓冲区的方法是正确的。
问题是哪个没有达到我的预期。
我现在可以使用它了。 然而,应该注意的是,这种方法是传统的方法。 如果你想用“新方法”做这件事,你可能应该使用。
它的支持始于Windows 10 1809和Windows Server 2019 还应注意,与匿名管道相比,通过控制台屏幕缓冲区读取进程/程序输出的方法有其缺陷和两个明显的缺点:
MAXSHORT-1
)来规避这两种情况,然后等待进程/程序完成。这对我来说已经足够好了,因为我不需要分析或处理彩色输出,只需要在控制台窗口中显示它,而控制台窗口本身仅限于
MAXSHORT-1
行。在其他任何情况下,我都会使用管道,并建议其他人也这样做强> 这是一个没有任何错误处理的简短版本,可以在不受干扰的情况下并行执行(前提是TStream对象由线程或线程安全拥有):
@J。。。谢谢你的意见。我编辑了问题并删除了不必要的信息。我希望现在好多了。我还将深入研究SetConsoleMode及其周围环境。这并不是对您问题的回答,但我想指出一个常见问题,人们在代码中读取子进程的输出。他们使用WaitForSingleObject之类的工具来尝试确定生成的子进程何时退出。这对你的问题没有帮助;我只是想摆脱
ReadPipe
/MsgWait
一次一个bug。您的链接“将彩色控制台输出捕获到WPF应用程序”描述问题和解决方案-您必须解释控制/转义码,并单独跟踪每个流的当前属性设置,因为属性在更改之前一直适用。@Brian我的问题是:输出的位和字节中没有控制/转义码。正如我在问题中所写的那样,我只收到普通的旧文本。@Ianboy,我需要考虑一下。我不喜欢ReadFile的阻塞特性。这就是为什么我在阅读之前会定期循环并查看管道中的内容。这会给线程带来一些开销,并可能会给子进程带来延迟,但会让我完全控制线程。如果我理解正确,我的设计决策仍将导致子进程能够完成。在某个时刻,子进程将写入所有内容,我将读取所有内容,WaitForSingleObject将返回除WAIT_TIMEOUT之外的内容。还是我错过了什么?
ConsoleHandle := CreateConsoleScreenBuffer(
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
@SecurityAttributes,
CONSOLE_TEXTMODE_BUFFER,
nil);
//...
StartupInfo.hStdOutput := ConsoleHandle;
StartupInfo.hStdError := ConsoleHandle;
//...
ConsoleOutput := TMemoryStream.Create
ConsoleOutput.Size := MAXWORD;
ConsoleOutput.Position := 0;
ReadConsole(ConsoleHandle, ConsoleOutput.Memory, ConsoleOutput.Size, CharsRead, nil) // Doesn't read anything and returns with System Error Code 6.
procedure CreateProcessWithConsoleCapture(const aAppName, aCMD, aDefaultDir: PChar;
const CapturedOutput: TStream);
const
CONSOLE_SCREEN_BUFFER_SIZE_Y = MAXSHORT - 1;
var
SecurityAttributes: TSecurityAttributes;
ConsoleHandle: THandle;
StartupInfo: TStartupInfo;
ProcessInformation: TProcessInformation;
CharsRead: Cardinal;
BufferSize, Origin: TCoord;
ConsoleScreenBufferInfo: TConsoleScreenBufferInfo;
Buffer: array of TCharInfo;
ReadRec: TSmallRect;
begin
SecurityAttributes.nLength := SizeOf(SecurityAttributes);
SecurityAttributes.lpSecurityDescriptor := Nil;
SecurityAttributes.bInheritHandle := True;
ConsoleHandle := CreateConsoleScreenBuffer(
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
@SecurityAttributes,
CONSOLE_TEXTMODE_BUFFER,
nil);
try
GetConsoleScreenBufferInfo(ConsoleHandle, ConsoleScreenBufferInfo);
BufferSize.X := ConsoleScreenBufferInfo.dwSize.X;
BufferSize.Y := CONSOLE_SCREEN_BUFFER_SIZE_Y;
SetConsoleScreenBufferSize(ConsoleHandle, BufferSize);
Origin.X := 0;
Origin.Y := 0;
FillConsoleOutputCharacter(ConsoleHandle, #0, BufferSize.X * BufferSize.Y, Origin, CharsRead);
SetStdHandle(STD_OUTPUT_HANDLE, ConsoleHandle);
FillChar(StartupInfo, Sizeof(StartupInfo), 0);
StartupInfo.cb := SizeOf(StartupInfo);
StartupInfo.hStdOutput := ConsoleHandle;
StartupInfo.hStdError := ConsoleHandle;
StartupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
StartupInfo.dwFlags := STARTF_USESTDHANDLES or STARTF_FORCEOFFFEEDBACK;
CreateProcess(aAppName, aCMD,
@SecurityAttributes, @SecurityAttributes,
True, NORMAL_PRIORITY_CLASS,
nil, aDefaultDir,
StartupInfo, ProcessInformation);
try
WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
GetConsoleScreenBufferInfo(ConsoleHandle, ConsoleScreenBufferInfo);
BufferSize.X := ConsoleScreenBufferInfo.dwSize.X;
BufferSize.Y := ConsoleScreenBufferInfo.dwCursorPosition.Y;
if ConsoleScreenBufferInfo.dwCursorPosition.X > 0 then
begin
Inc(BufferSize.Y);
end;
ReadRec.Left := 0;
ReadRec.Top := 0;
ReadRec.Right := BufferSize.X - 1;
ReadRec.Bottom := BufferSize.Y - 1;
SetLength(Buffer, BufferSize.X * BufferSize.Y);
ReadConsoleOutput(ConsoleHandle, @Buffer[0], BufferSize, Origin, ReadRec);
CharsRead := SizeOf(TCharInfo) * (ReadRec.Right - ReadRec.Left + 1) * (ReadRec.Bottom - ReadRec.Top + 1);
if CharsRead > 0 then
begin
CapturedOutput.Size := CapturedOutput.Size + CharsRead;
CapturedOutput.WriteBuffer(Buffer[0], CharsRead);
end;
finally
CloseHandle(ProcessInformation.hProcess);
CloseHandle(ProcessInformation.hThread);
end;
finally
CloseHandle(ConsoleHandle);
end;
end;