如何将二进制gbak输出重定向到Delphi流? [英] How to redirect binary gbak output to a Delphi stream?

查看:219
本文介绍了如何将二进制gbak输出重定向到Delphi流?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望Firebird备份工具gbak将其输出写入Delphi流(不含中间文件)。有一个命令行参数写入stdout而不是一个文件。然后我使用JEDI的 JclSysUtils 中的 Execute 方法启动gbak并处理该输出。



它看起来像这样:

  procedure DoBackup; 
var
LBackupAbortFlag:Boolean;
LBackupStream:TStringStream;
begin
LBackupAbortFlag:= False;
LBackupStream:= TStringStream.Create;
尝试
执行('C:\path to\gbak.exe-b -t -v -user SYSDBA -paspw< db> stdout',
LBackupStream.WriteString,//应该处理stdout(备份)
SomeMemo.Lines.Append,//应该处理stderr(log)
True,//备份是raw
False,/ / Log不是
@LBackupAbortFlag);
LBackupStream.SaveToFile('C:\path to\output.fbk');
finally
LBackupStream.Free;
结束
结束

问题是输出文件太小,无法包含实际备份。仍然看到文件内容的元素。我尝试了不同的流类型,但这似乎并没有什么不同。这里可能会出现什么问题?



更新



要清楚:其他解决方案也是欢迎的。最重要的是,我需要一些可靠的东西。这就是为什么我和JEDI一起去了,而不是重塑这样的事情。那么,如果不是太复杂,那将是很好的。

解决方案

当你希望合并时,我的第一个答案是有效的stdout和stderr。但是,如果你需要保持这些分开,这种方法是没有用的。我现在可以从更仔细的阅读你的问题和你的意见中看出,你希望保持两个输出流分开。



现在,它不是完全直截了当地扩大了我的第一个答案来解决这个问题。问题是那里的代码使用阻塞I / O。如果您需要维修两条管道,就会有明显的冲突。 Windows中常用的解决方案是异步I / O,在Windows世界中称为重叠I / O。然而,异步I / O比阻塞I / O要复杂得多。



所以,我将提出一种替代方法,它仍然使用阻塞I / O.如果我们要服务多个管道,并且我们要使用阻塞I / O,那么明显的结论是每个管道需要一个线程。这很容易实现 - 比异步选项容易得多。我们可以使用几乎相同的代码,但将阻塞读取循环移动到线程中。我的例子,这样重新工作,现在看起来像这样:

  {$ APPTYPE CONSOLE} 

使用
SysUtils,Classes,Windows;

type
TProcessOutputPipe = class
private
Frd:THandle;
Fwr:THandle;
public
构造函数创建;
析构函数覆盖
property rd:THandle read Frd;
属性wr:THandle读取Fwr;
procedure CloseWritePipe;
结束

构造函数TProcessOutputPipe.Create;
const
PipeSecurityAttributes:TSecurityAttributes =(
nLength:SizeOf(TSecurityAttributes);
bInheritHandle:True
);
开始
继承;
Win32Check(CreatePipe(Frd,Fwr,@PipeSecurityAttributes,0));
Win32Check(SetHandleInformation(Frd,HANDLE_FLAG_INHERIT,0)); //不继承管道的读取句柄
end;

析构函数TProcessOutputPipe.Destroy;
begin
CloseHandle(Frd);
如果Fwr<> 0则
CloseHandle(Fwr);
继承;
结束

procedure TProcessOutputPipe.CloseWritePipe;
begin
CloseHandle(Fwr);
Fwr:= 0;
结束

type
TReadPipeThread = class(TThread)
private
FPipeHandle:THandle;
FStream:TStream;
protected
procedure Execute;覆盖
public
构造函数创建(PipeHandle:THandle; Stream:TStream);
结束

构造函数TReadPipeThread.Create(PipeHandle:THandle; Stream:TStream);
begin
继承Create(False);
FPipeHandle:= PipeHandle;
FStream:= Stream;
结束

程序TReadPipeThread.Execute;
var
字节缓冲区:数组[0..4096-1];
BytesRead:DWORD;
begin
而ReadFile(FPipeHandle,Buffer,SizeOf(Buffer),BytesRead,nil)和(BytesRead<> 0)开始
FStream.WriteBuffer(Buffer,BytesRead);
结束
结束

函数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
如果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或STARTF_USESTDHANDLES;
StartupInfo.wShowWindow:= SW_HIDE;
StartupInfo.hStdOutput:= stdoutPipe.wr;
StartupInfo.hStdError:= stderrPipe.wr;
Win32Check(CreateProcess(lpApplicationName,PChar(ModfiableCommandLine),nil,nil,True,
CREATE_NO_WINDOW或NORMAL_PRIORITY_CLASS,nil,nil,StartupInfo,ProcessInfo));

stdoutPipe.CloseWritePipe; //使进程能够终止
stderrPipe.CloseWritePipe; //使进程能够终止

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;
结束
结束

程序测试;
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;
结束
finally
stdout.Free;
结束
结束

开始
测试;
结束。

如果您希望添加对取消的支持,那么您只需添加一个调用 TerminateProcess 当用户取消。这将使所有事情都停止,该函数将返回您提供给 TerminateProcess 的退出代码。我现在犹豫为你建议一个取消框架,但我认为这个答案中的代码现在非常接近满足你的要求。


I want the Firebird backup tool gbak to write its output to a Delphi stream (with no intermediate file). There is a command line parameter to write to stdout rather than a file. I then use the Execute method in JEDI's JclSysUtils to launch gbak and process that output.

It looks like this:

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;

The problem is that the output file is way too small to contain that actual backup. Still I see elements of the file's content. I tried different stream types, but that doesn't seem to make a difference. What could be going wrong here?

Update

To be clear: other solutions are welcome as well. Most of all, I need something reliable. That's why I went with JEDI in the first place, not to reinvent such a thing. Then, it would be nice, if it would be not too complicated.

解决方案

My first answer is effective when you wish to merge stdout and stderr. However, if you need to keep these separate, that approach is no use. And I can now see, from a closer reading of your question, and your comments, that you do wish to keep the two output streams separate.

Now, it is not completely straightforward to extend my first answer to cover this. The problem is that the code there uses blocking I/O. And if you need to service two pipes, there is an obvious conflict. A commonly used solution in Windows is asynchronous I/O, known in the Windows world as overlapped I/O. However, asynchronous I/O is much more complex to implement than blocking I/O.

So, I'm going to propose an alternative approach that still uses blocking I/O. If we want to service multiple pipes, and we want to use blocking I/O then the obvious conclusion is that we need one thread for each pipe. This is easy to implement – much easier than the asynchronous option. We can use almost identical code but move the blocking read loops into threads. My example, re-worked in this way, now looks like this:

{$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, nil, True,
      CREATE_NO_WINDOW or NORMAL_PRIORITY_CLASS, nil, nil, 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.

If you wish to add support for cancelling, then you would simply add in a call to TerminateProcess when the user cancelled. This would bring everything to a halt, and the function would return the exit code that you supplied to TerminateProcess. I'm hesitant right now to suggest a cancellation framework for you, but I think that the code in this answer is now pretty close to meeting your requirements.

这篇关于如何将二进制gbak输出重定向到Delphi流?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆