Obtenir la sortie d'une application shell / dos dans une application Delphi

j'ai une application en ligne de commande codée en delphi que je dois appeler à partir d'une application de bureau normale (également codée en delphi). En bref, je veux appeler l'application en ligne de commande et afficher le texte qu'elle produit "en direct" dans une liste de diffusion.

Cela fait longtemps que je n'ai pas joué avec la coquille, mais je me souviens très bien que pour saisir le texte d'une application en ligne de commande - je dois utiliser le symbole de la pipe">". Comme ceci:

C:/mycmdapp.EXE >c:/result.txt

cela prendra n'importe quel texte imprimé sur le shell (en utilisant writeLn) et le déchargera dans un fichier texte appelé" result.txt".

mais.. (et voici le cornichon), je veux un résultat en direct plutôt qu'un dossier d'arriéré. Un exemple typique est le compilateur Delphi lui - même-qui parvient à rapporter à L'IDE ce qui se passe. Si ma mémoire me sert correctement, il me semble me rappeler que je dois créer un canal" pipe" (?), puis attribuer le nom de pipe au shell appel.

j'ai essayé de google ceci mais j'étais honnêtement incertain de la façon de le formuler. J'espère que quelqu'un de la communauté pourra me guider dans la bonne direction.

mise à jour : cette question pourrait être identique à comment exécuter un programme en ligne de commande dans Delphi? . Certaines des réponses correspondent à ce que je cherche, bien que le titre et la question elle-même ne soient pas identiques.

29
demandé sur ROMANIA_engineer 2012-02-03 01:06:51

2 réponses

comme souvent Zarco Gajic a une solution: capturer la sortie à partir D'un DOS (commande/console) fenêtre . Ceci est une copie de son article pour référence future:

l'exemple exécute 'chkdsk.Exec:\' et affiche la sortie sur Memo1. Mettez un TMemo (Memo1) et un TButton (Button1) sur votre formulaire. Mettez ce code dans la procédure d'événement OnCLick pour Button1 :

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;

mise à jour: L'exemple ci-dessus lit la sortie en une seule étape. Voici un autre exemple de DelphiDabbler montrant comment la sortie peut être lue pendant que le processus est encore en cours d'exécution:

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;
46
répondu Uwe Raabe 2017-08-30 12:27:08

vous avez probablement déjà le code sur votre disque dur: la fonction Execute dans l'unité JclSysUtils de la bibliothèque de Code JEDI fait ce dont vous avez besoin:

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

vous pouvez lui fournir une procédure de rappel:

TTextHandler = procedure(const Text: string) of object;

14
répondu gogowitsch 2013-04-03 08:40:31