Получение вывода из приложения shell / dos в приложение Delphi

30

У меня есть приложение командной строки, закодированное в delphi, которое мне нужно вызвать из обычного настольного приложения (также закодированного в delphi). Короче говоря, я хочу вызвать приложение командной строки и отобразить текст, который он выводит "live" в списке.

Прошло много времени с тех пор, как я играл с оболочкой, но я отчетливо помню, что для того, чтобы захватить текст из приложения командной строки, я должен использовать символ трубы " > ". Вот так:

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

Это приведет к тому, что любой текст будет напечатан в оболочке (с использованием writeLn) и выгрузите его в текстовый файл под названием "result.txt".

Но.. (и вот идет рассол), мне нужен живой результат, а не файл журнала. Типичным примером является сам компилятор Delphi, которому удается сообщать обратно в среду IDE, что происходит. Если моя память правильно меня обслуживает, мне кажется, что я должен создать канал "pipe" (?), А затем назначить имя канала вызову оболочки.

Я попытался это сделать, но я, честно говоря, не знал, как его сформулировать. Надеюсь, кто-то из сообщества может указать мне в правильном направлении.

Обновлено. Этот вопрос может быть идентичным Как запустить программу командной строки в Delphi?. Некоторые ответы соответствуют тому, что я ищу, хотя сам титул и сам вопрос не идентичны.

  • 5
    Классы консольных приложений - выглядит многообещающе
  • 0
    Я использую Delphi XE2 (хотя у меня есть большинство версий Delphi от Delphi 4 и выше). Разве не было одной из главных новостей о том, что Delphi 2010+ двоично совместим с XE и XE2? В этом случае классы должны работать. Большое вам спасибо за ваши отзывы!
Показать ещё 4 комментария
Теги:
shellexecute
pipe
windows-shell

2 ответа

44
Лучший ответ

Как всегда, у Zarco Gajic есть решение: Захват вывода из окна DOS (команда/консоль). Это копия его статьи для дальнейшего использования:

В примере выполняется 'chkdsk.exe c: \' и выводится вывод в Memo1. Поместите в вашу форму TMemo (Memo1) и TButton (Button1). Поместите этот код в процедуру события OnCLick для 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 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 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;

Update: В приведенном выше примере считывается вывод за один шаг. Вот еще один пример из DelphiDabbler, показывающий, как вывод может быть прочитан, пока процесс все еще запущен:

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
    Это идеально! Спасибо вам большое! Я не думал о проверке delphi.about.com, отлично!
  • 3
    @JonLennartAasenden, я добавил второй пример, демонстрирующий лучший подход.
Показать ещё 16 комментариев
14

Вероятно, у вас уже есть код на вашем жестком диске: функция Execute в модуле JclSysUtils JCL (JEDI Code Library) делает то, что вам нужно:

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

Вы можете предоставить ему процедуру обратного вызова:
TTextHandler = procedure(const Text: string) of object;

Ещё вопросы

Сообщество Overcoder
Наверх
Меню