uso tuberias salida redireccionar redireccionamiento pipes para pagina otra operador logs index guardar filtros ejemplos comando archivo delphi delphi-2010 firebird firebird2.5 jedi

tuberias - ¿Cómo redirigir la salida binaria de gbak a una transmisión Delphi?



tuberias y filtros en linux (2)

Espero que su código esté fallando porque intenta poner datos binarios a través de una secuencia orientada al texto. En cualquier caso, es bastante simple resolver su problema con un par de llamadas a la API de Win32. No veo ningún motivo convincente para utilizar componentes de terceros solo para esta tarea.

Esto es lo que necesita hacer:

  1. Cree una tubería que usará como un canal de comunicación entre los dos procesos.
  2. Cree el proceso gbak y organice que su stdout sea el final de escritura de la tubería.
  3. Lea desde el final de lectura de la tubería.

Aquí hay un programa de demostración simple:

{$APPTYPE CONSOLE} uses SysUtils, Classes, Windows; procedure ReadOutputFromExternalProcess(const ApplicationName, CommandLine: string; Stream: TStream); const PipeSecurityAttributes: TSecurityAttributes = ( nLength: SizeOf(PipeSecurityAttributes); bInheritHandle: True ); var hstdoutr, hstdoutw: THandle; StartupInfo: TStartupInfo; ProcessInfo: TProcessInformation; lpApplicationName: PChar; ModfiableCommandLine: string; Buffer: array [0..4096-1] of Byte; BytesRead: DWORD; begin if ApplicationName='''' then begin lpApplicationName := nil; end else begin lpApplicationName := PChar(ApplicationName); end; ModfiableCommandLine := CommandLine; UniqueString(ModfiableCommandLine); Win32Check(CreatePipe(hstdoutr, hstdoutw, @PipeSecurityAttributes, 0)); Try Win32Check(SetHandleInformation(hstdoutr, HANDLE_FLAG_INHERIT, 0));//don''t inherit read handle of pipe ZeroMemory(@StartupInfo, SizeOf(StartupInfo)); StartupInfo.cb := SizeOf(StartupInfo); StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES; StartupInfo.wShowWindow := SW_HIDE; StartupInfo.hStdOutput := hstdoutw; StartupInfo.hStdError := hstdoutw; if not CreateProcess( lpApplicationName, PChar(ModfiableCommandLine), nil, nil, True, CREATE_NO_WINDOW or NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo ) then begin RaiseLastOSError; end; CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); CloseHandle(hstdoutw);//close the write end of the pipe so that the process is able to terminate hstdoutw := 0; while ReadFile(hstdoutr, Buffer, SizeOf(Buffer), BytesRead, nil) and (BytesRead<>0) do begin Stream.WriteBuffer(Buffer, BytesRead); end; Finally CloseHandle(hstdoutr); if hstdoutw<>0 then begin CloseHandle(hstdoutw); end; End; end; procedure Test; var Stream: TFileStream; begin Stream := TFileStream.Create(''C:/Desktop/out.txt'', fmCreate); Try ReadOutputFromExternalProcess('''', ''cmd /c dir /s C:/Windows/system32'', Stream); Finally Stream.Free; End; end; begin Test; end.

Quiero que la herramienta de copia de seguridad de Firebird, gbak, escriba su salida en una secuencia Delphi (sin ningún archivo intermedio). Hay un parámetro de línea de comando para escribir en stdout en lugar de en un archivo. Luego uso el método Execute en JclSysUtils de JEDI para lanzar gbak y procesar ese resultado.

Se parece a esto:

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;

El problema es que el archivo de salida es demasiado pequeño para contener esa copia de seguridad real. Todavía veo elementos del contenido del archivo. Intenté diferentes tipos de transmisión, pero eso no parece marcar la diferencia. ¿Qué podría estar yendo mal aquí?

Actualizar

Para ser claros: también son bienvenidas otras soluciones. Más que nada, necesito algo confiable. Es por eso que fui con JEDI en primer lugar, no para reinventar tal cosa. Entonces, sería bueno, si no fuera demasiado complicado.


Mi primera respuesta es efectiva cuando desea fusionar stdout y stderr. Sin embargo, si necesita mantenerlos separados, ese enfoque no sirve de nada. Y ahora puedo ver, a partir de una lectura más cercana de su pregunta y sus comentarios, que desea mantener las dos secuencias de salida separadas.

Ahora, no es completamente sencillo extender mi primera respuesta para cubrir esto. El problema es que el código allí usa bloqueo de E / S. Y si necesita dar servicio a dos tuberías, hay un conflicto obvio. Una solución comúnmente utilizada en Windows es la E / S asíncrona, conocida en el mundo de Windows como E / S superpuesta. Sin embargo, la E / S asíncrona es mucho más compleja de implementar que el bloqueo de E / S.

Por lo tanto, voy a proponer un enfoque alternativo que todavía utilice bloqueo de E / S. Si queremos dar servicio a varios conductos, y queremos usar E / S de bloqueo, la conclusión obvia es que necesitamos un hilo para cada tubería. Esto es fácil de implementar, mucho más fácil que la opción asincrónica. Podemos usar código casi idéntico pero mover los bucles de lectura de bloqueo en hilos. Mi ejemplo, re-trabajado de esta manera, ahora se ve así:

{$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.

Si desea agregar soporte para cancelar, simplemente debe agregar una llamada a TerminateProcess cuando el usuario cancela. Esto detendría todo, y la función devolvería el código de salida que proporcionó a TerminateProcess . En este momento, no estoy seguro de sugerirle un marco de cancelación, pero creo que el código en esta respuesta está ahora muy cerca de cumplir con sus requisitos.