微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何将二进制gbak输出重定向到Delphi流?

我希望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的退出代码.我现在犹豫为你建议一个取消框架,但我认为这个答案中的代码现在非常接近满足你的要求.

原文地址:https://www.jb51.cc/delphi/103053.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐