我希望Firebird备份工具gbak将其输出写入Delphi流(没有中间文件).有一个命令行参数可以写入stdout而不是文件.然后我在JEDI的JclSysUtils中使用Execute方法来启动gbak并处理该输出.
它看起来像这样:
procedure DoBackup; var LBackupAbortFlag: Boolean; LBackupStream: TStringStream; begin LBackupAbortFlag := False; LBackupStream := TStringStream.Create; try Execute('"C:\path to\gbak.exe" -b -t -v -user SYSDBA -pas "pw" <db> stdout',LBackupStream.WriteString,// Should process stdout (backup) SomeMemo.Lines.Append,// Should process stderr (log) True,// Backup is "raw" False,// Log is not @LBackupAbortFlag); LBackupStream.SaveToFile('C:\path to\output.fbk'); finally LBackupStream.Free; end; end;
问题是输出文件太小而无法包含实际备份.我仍然看到文件内容的元素.我尝试了不同的流类型,但这似乎没有什么区别.这可能会出错?
更新
需要明确的是:其他解决方案也是受欢迎的.最重要的是,我需要一些可靠的东西.这就是为什么我首先选择JEDI,而不是重新发明这样的事情.那么,如果它不会太复杂,那就太好了.
解决方法
当您希望合并stdout和stderr时,我的第一个答案是有效的.但是,如果您需要将这些分开,那么这种方法是没有用的.我现在可以通过仔细阅读您的问题和您的评论,看到您确实希望将两个输出流分开.
现在,扩展我的第一个答案以涵盖这一点并非完全直截了当.问题是那里的代码使用阻塞I / O.如果你需要维修两个管道,那就有明显的冲突. Windows中常用的解决方案是异步I / O,在Windows世界中称为重叠I / O.但是,异步I / O的实现要比阻塞I / O复杂得多.
所以,我将提出一种仍然使用阻塞I / O的替代方法.如果我们想要服务多个管道,并且我们想要使用阻塞I / O,那么显而易见的结论是每个管道需要一个线程.这很容易实现 – 比异步选项容易得多.我们可以使用几乎相同的代码,但将阻塞读取循环移动到线程中.我的例子,以这种方式重新工作,现在看起来像这样:
{$APPTYPE CONSOLE} uses SysUtils,Classes,Windows; type TProcessOutputPipe = class private Frd: THandle; Fwr: THandle; public constructor Create; destructor Destroy; override; property rd: THandle read Frd; property wr: THandle read Fwr; procedure CloseWritePipe; end; constructor TProcessOutputPipe.Create; const PipeSecurityAttributes: TSecurityAttributes = ( nLength: SizeOf(TSecurityAttributes); bInheritHandle: True ); begin inherited; Win32Check(CreatePipe(Frd,Fwr,@PipeSecurityAttributes,0)); Win32Check(SetHandleInformation(Frd,HANDLE_FLAG_INHERIT,0));//don't inherit read handle of pipe end; destructor TProcessOutputPipe.Destroy; begin CloseHandle(Frd); if Fwr<>0 then CloseHandle(Fwr); inherited; end; procedure TProcessOutputPipe.CloseWritePipe; begin CloseHandle(Fwr); Fwr := 0; end; type TReadPipeThread = class(TThread) private FPipeHandle: THandle; FStream: TStream; protected procedure Execute; override; public constructor Create(PipeHandle: THandle; Stream: TStream); end; constructor TReadPipeThread.Create(PipeHandle: THandle; Stream: TStream); begin inherited Create(False); FPipeHandle := PipeHandle; FStream := Stream; end; procedure TReadPipeThread.Execute; var Buffer: array [0..4096-1] of Byte; BytesRead: DWORD; begin while ReadFile(FPipeHandle,Buffer,SizeOf(Buffer),BytesRead,nil) and (BytesRead<>0) do begin FStream.WriteBuffer(Buffer,BytesRead); end; end; function ReadOutputFromExternalProcess(const ApplicationName,CommandLine: string; stdout,stderr: TStream): DWORD; var stdoutPipe,stderrPipe: TProcessOutputPipe; stdoutThread,stderrThread: TReadPipeThread; StartupInfo: TStartupInfo; ProcessInfo: TProcessInformation; lpApplicationName: PChar; ModfiableCommandLine: string; begin if ApplicationName='' then lpApplicationName := nil else lpApplicationName := PChar(ApplicationName); ModfiableCommandLine := CommandLine; UniqueString(ModfiableCommandLine); stdoutPipe := nil; stderrPipe := nil; stdoutThread := nil; stderrThread := nil; try stdoutPipe := TProcessOutputPipe.Create; stderrPipe := TProcessOutputPipe.Create; ZeroMemory(@StartupInfo,SizeOf(StartupInfo)); StartupInfo.cb := SizeOf(StartupInfo); StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES; StartupInfo.wShowWindow := SW_HIDE; StartupInfo.hStdOutput := stdoutPipe.wr; StartupInfo.hStdError := stderrPipe.wr; Win32Check(CreateProcess(lpApplicationName,PChar(ModfiableCommandLine),nil,True,CREATE_NO_WINDOW or NORMAL_PRIORITY_CLASS,StartupInfo,ProcessInfo)); stdoutPipe.CloseWritePipe;//so that the process is able to terminate stderrPipe.CloseWritePipe;//so that the process is able to terminate stdoutThread := TReadPipeThread.Create(stdoutPipe.rd,stdout); stderrThread := TReadPipeThread.Create(stderrPipe.rd,stderr); stdoutThread.WaitFor; stderrThread.WaitFor; Win32Check(WaitForSingleObject(ProcessInfo.hProcess,INFINITE)=WAIT_OBJECT_0); Win32Check(GetExitCodeProcess(ProcessInfo.hProcess,Result)); finally stderrThread.Free; stdoutThread.Free; stderrPipe.Free; stdoutPipe.Free; end; end; procedure Test; var stdout,stderr: TFileStream; ExitCode: DWORD; begin stdout := TFileStream.Create('C:\Desktop\stdout.txt',fmCreate); try stderr := TFileStream.Create('C:\Desktop\stderr.txt',fmCreate); try ExitCode := ReadOutputFromExternalProcess('','cmd /c dir /s C:\Windows\system32',stdout,stderr); finally stderr.Free; end; finally stdout.Free; end; end; begin Test; end.
如果您希望添加对取消的支持,那么您只需在用户取消时添加对TerminateProcess的调用.这将使一切停止,函数将返回您提供给TerminateProcess的退出代码.我现在犹豫为你建议一个取消框架,但我认为这个答案中的代码现在非常接近满足你的要求.