2012-02-02 17 views
28

Tengo una aplicación de línea de comandos codificada en delphi que debo llamar desde una aplicación de escritorio normal (también codificada en delphi). En resumen, quiero llamar a la aplicación de línea de comandos y mostrar el texto que emite "en vivo" en un cuadro de lista.Obtener resultados desde una aplicación shell/dos en una aplicación Delphi

Han pasado años desde que jugué con el shell, pero recuerdo claramente que para tomar el texto de una aplicación de línea de comandos, tengo que usar el símbolo de tubería ">". De esta manera:

C: /mycmdapp.exe> ​​c: /result.txt

Esto tomará cualquier texto impreso en la cáscara (usando Writeln) y lo descarga a un archivo de texto llamado "Result.txt".

Pero ... (y aquí viene la salmuera), quiero un resultado en vivo en lugar de un archivo de atrasos. Un ejemplo típico es el propio compilador Delphi, que se las arregla para informar al IDE lo que está sucediendo. Si mi memoria me sirve correctamente, parece recordar que debo crear un canal de "canalización" (?) Y luego asignar el nombre de canal a la llamada de shell.

He intentado googlear esto pero sinceramente no estaba seguro de cómo formularlo. Espero que alguien de la comunidad pueda señalarme en la dirección correcta.

Actualizado: Esta pregunta podría ser idéntica a How do I run a command-line program in Delphi?. Algunas de las respuestas se ajustan a lo que estoy buscando, aunque el título y la pregunta en sí no son idénticos.

+5

[Clases de aplicación de consola Runner] (ht tp: //www.delphidabbler.com/software/consoleapp/main? mid = 3.2) - parece prometedor – kobik

+0

Estoy usando Delphi XE2 (aunque tengo la mayoría de las versiones de Delphi de Delphi 4 y posteriores). ¿No fue una de las grandes noticias anteriores que Delphi 2010+ es binario compatible con XE y XE2? En ese caso, las clases deberían funcionar. ¡Muchas gracias por tus comentarios! –

+0

@Jon: No, eso es incorrecto. No hay compatibilidad binaria entre las versiones del compilador Delphi (con la única excepción entre D6 y D7). No puede usar archivos .dcu compilados por D2010 con ninguna otra versión de Delphi sin volver a compilar la fuente. –

Respuesta

45

Como siempre, Zarco Gajic tiene una solución: Capture the output from a DOS (command/console) Window. Esta es una copia de su artículo para referencia futura:

El ejemplo se ejecuta 'chkdsk.exe c: \' y muestra la salida a Memo1. Ponga un TMemo (Memo1) y un TButton (Button1) en su formulario. Pon este código en el procedimiento de evento para OnCLickButton1:

procedure RunDosInMemo(DosApp: string; AMemo:TMemo); 
const 
    READ_BUFFER_SIZE = 2400; 
var 
    Security: TSecurityAttributes; 
    readableEndOfPipe, writeableEndOfPipe: THandle; 
    start: TStartUpInfo; 
    ProcessInfo: TProcessInformation; 
    Buffer: PAnsiChar; 
    BytesRead: DWORD; 
    AppRunning: DWORD; 
begin 
    Security.nLength := SizeOf(TSecurityAttributes); 
    Security.bInheritHandle := True; 
    Security.lpSecurityDescriptor := nil; 

    if CreatePipe({var}readableEndOfPipe, {var}writeableEndOfPipe, @Security, 0) then 
    begin 
     Buffer := AllocMem(READ_BUFFER_SIZE+1); 
     FillChar(Start, Sizeof(Start), #0); 
     start.cb := SizeOf(start); 

     // Set up members of the STARTUPINFO structure. 
     // This structure specifies the STDIN and STDOUT handles for redirection. 
     // - Redirect the output and error to the writeable end of our pipe. 
     // - We must still supply a valid StdInput handle (because we used STARTF_USESTDHANDLES to swear that all three handles will be valid) 
     start.dwFlags := start.dwFlags or STARTF_USESTDHANDLES; 
     start.hStdInput := GetStdHandle(STD_INPUT_HANDLE); //we're not redirecting stdInput; but we still have to give it a valid handle 
     start.hStdOutput := writeableEndOfPipe; //we give the writeable end of the pipe to the child process; we read from the readable end 
     start.hStdError := writeableEndOfPipe; 

     //We can also choose to say that the wShowWindow member contains a value. 
     //In our case we want to force the console window to be hidden. 
     start.dwFlags := start.dwFlags + STARTF_USESHOWWINDOW; 
     start.wShowWindow := SW_HIDE; 

     // Don't forget to set up members of the PROCESS_INFORMATION structure. 
     ProcessInfo := Default(TProcessInformation); 

     //WARNING: The unicode version of CreateProcess (CreateProcessW) can modify the command-line "DosApp" string. 
     //Therefore "DosApp" cannot be a pointer to read-only memory, or an ACCESS_VIOLATION will occur. 
     //We can ensure it's not read-only with the RTL function: UniqueString 
     UniqueString({var}DosApp); 

     if CreateProcess(nil, PChar(DosApp), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, start, {var}ProcessInfo) then 
     begin 
      //Wait for the application to terminate, as it writes it's output to the pipe. 
      //WARNING: If the console app outputs more than 2400 bytes (ReadBuffer), 
      //it will block on writing to the pipe and *never* close. 
      repeat 
       Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 100); 
       Application.ProcessMessages; 
      until (Apprunning <> WAIT_TIMEOUT); 

      //Read the contents of the pipe out of the readable end 
      //WARNING: if the console app never writes anything to the StdOutput, then ReadFile will block and never return 
      repeat 
       BytesRead := 0; 
       ReadFile(readableEndOfPipe, Buffer[0], READ_BUFFER_SIZE, {var}BytesRead, nil); 
       Buffer[BytesRead]:= #0; 
       OemToAnsi(Buffer,Buffer); 
       AMemo.Text := AMemo.text + String(Buffer); 
      until (BytesRead < READ_BUFFER_SIZE); 
     end; 
     FreeMem(Buffer); 
     CloseHandle(ProcessInfo.hProcess); 
     CloseHandle(ProcessInfo.hThread); 
     CloseHandle(readableEndOfPipe); 
     CloseHandle(writeableEndOfPipe); 
    end; 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
begin {button 1 code} 
    RunDosInMemo('chkdsk.exe c:\',Memo1); 
end; 

Actualización: el ejemplo anterior se lee la salida en un solo paso. Este es otro ejemplo de DelphiDabbler que muestra cómo la salida se puede leer mientras que el proceso todavía está en ejecución:

function GetDosOutput(CommandLine: string; Work: string = 'C:\'): string; 
var 
    SA: TSecurityAttributes; 
    SI: TStartupInfo; 
    PI: TProcessInformation; 
    StdOutPipeRead, StdOutPipeWrite: THandle; 
    WasOK: Boolean; 
    Buffer: array[0..255] of AnsiChar; 
    BytesRead: Cardinal; 
    WorkDir: string; 
    Handle: Boolean; 
begin 
    Result := ''; 
    with SA do begin 
    nLength := SizeOf(SA); 
    bInheritHandle := True; 
    lpSecurityDescriptor := nil; 
    end; 
    CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0); 
    try 
    with SI do 
    begin 
     FillChar(SI, SizeOf(SI), 0); 
     cb := SizeOf(SI); 
     dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES; 
     wShowWindow := SW_HIDE; 
     hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin 
     hStdOutput := StdOutPipeWrite; 
     hStdError := StdOutPipeWrite; 
    end; 
    WorkDir := Work; 
    Handle := CreateProcess(nil, PChar('cmd.exe /C ' + CommandLine), 
          nil, nil, True, 0, nil, 
          PChar(WorkDir), SI, PI); 
    CloseHandle(StdOutPipeWrite); 
    if Handle then 
     try 
     repeat 
      WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil); 
      if BytesRead > 0 then 
      begin 
      Buffer[BytesRead] := #0; 
      Result := Result + Buffer; 
      end; 
     until not WasOK or (BytesRead = 0); 
     WaitForSingleObject(PI.hProcess, INFINITE); 
     finally 
     CloseHandle(PI.hThread); 
     CloseHandle(PI.hProcess); 
     end; 
    finally 
    CloseHandle(StdOutPipeRead); 
    end; 
end; 
+0

¡Eso es perfecto! Muchas gracias! ¡No pensé en consultar delphi.about.com, excelente! –

+3

@JonLennartAasenden, agregué un segundo ejemplo que muestra un mejor enfoque. –

+0

Tu el hombre Uwe! ¡Gracias! –

13

Es probable que tenga el código en su disco duro ya: la función Execute en la unidad de la JCL JclSysUtils (Código JEDI biblioteca) hace lo que necesita:

function Execute(const CommandLine: string; OutputLineCallback: TTextHandler; 
    RawOutput: Boolean = False; AbortPtr: PBoolean = nil): Cardinal; 

le puede proveer con un procedimiento de devolución de llamada:
TTextHandler = procedure(const Text: string) of object;

Cuestiones relacionadas