Link to home
Start Free TrialLog in
Avatar of PeterDelphin
PeterDelphin

asked on

How to get the output from a console program in real-time?

In a Delphi XE7/8 program, I want to capture the output of a console program in REALTIME, e.g. inside a TMemo. For this I have created a test console program which outputs a text every 2 seconds:

program GetDosOutput_Console_Test;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

begin
  try
    Writeln('1 of 5');
    Sleep(2000);
    Writeln('2 of 5');
    Sleep(2000);
    Writeln('3 of 5');
    Sleep(2000);
    Writeln('4 of 5');
    Sleep(2000);
    Writeln('5 of 5');
    Sleep(2000);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Open in new window


Then I use a code I have found on http://stackoverflow.com/questions/9119999/getting-output-from-a-shell-dos-app-into-a-delphi-app

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;
            Form1.mmoOutput.Lines.Add(Result); // <<<<<<<<<<<<<<<<<<< output
          end;
        until not WasOK or (BytesRead = 0);
        WaitForSingleObject(PI.hProcess, INFINITE);
      finally
        CloseHandle(PI.hThread);
        CloseHandle(PI.hProcess);
      end;
  finally
    CloseHandle(StdOutPipeRead);
  end;
end;

Open in new window


Then I call the console program with this code:

GetDosOutput(edtConsoleExe.Text);

Open in new window


This works, HOWEVER the output in the memo is not in real-time (i.e. one line each two seconds) BUT once in one step when the console program closes:

User generated image
So how can I output the lines from the console program in REAL-TIME?
Avatar of Kyle Abrahams, PMP
Kyle Abrahams, PMP
Flag of United States of America image

try adding the processMessages After you get your result:

EG:
Form1.mmoOutput.Lines.Add(Result); // <<<<<<<<<<<<<<<<<<< output
Application.ProcessMessages;
Avatar of PeterDelphin
PeterDelphin

ASKER

Kyle, I did as you wrote. But it does not work, I still get no real-time output. Only the whole output when the console program terminates.
I also tried this one:

procedure GetThisDosOutput(const Text: string);
begin
  Form1.mmoOutput.Lines.Add(Text);
end;

procedure TForm1.btnStartConsoleExeClick(Sender: TObject);
begin
  JclSysUtils.Execute(edtConsoleExe.Text, nil, @GetThisDosOutput, False, ppNormal);
end;

Open in new window


However, with this code I always get this compiler error:

[dcc32 Error] GetDosOutput_Master_MainForm.pas(151): E2250 There is no overloaded version of 'Execute' that can be called with these arguments

Maybe someone could provide a working example using JclSysUtils.Execute?
There is also another code here: http://delphi.wikia.com/wiki/Capture_Console_Output_Realtime_To_Memo

However, also with this code I get a Delphi XE7 compiler error on the line with OemToAnsi(pBuffer, pBuffer);.

Maybe someone could give me a hint on how to make this code work?
There seems to be something wrong with my console program. I tried this code (answer from David Heffernan):

http://stackoverflow.com/questions/25723807/execute-dos-program-and-get-output-dynamically

With this code I still get no real-time output (only when the console program terminates) with my console program (see above), but I do get real-time output with a youtube-downloader console program (https://github.com/rg3/youtube-dl/blob/master/README.md)

WHY?
Try to replace Sleep(2000) by SoftDelay(2000):

procedure SoftDelay(ADelay: UInt64);
var
  TickCount: UInt64;
begin
  TickCount:=GetTickCount64;
  repeat
    Application.ProcessMessages;
  until GetTickCount64-TickCount > ADelay;
end;

Open in new window

Even with SoftDelay I don't get real-time output from GetDosOutput_Console_Test with the code of David Heffernan, only when GetDosOutput_Console_Test.exe terminates.
ASKER CERTIFIED SOLUTION
Avatar of Sinisa Vuk
Sinisa Vuk
Flag of Croatia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
why look on another site ?
epasquier seems to have stopped posting ... can't blame him, not much activity here anymore compared to before
https://www.experts-exchange.com/questions/27460239/get-stdout-and-stderr-of-an-exe-program-inside-Delphi.html
Thank you for the solution!

And sorry for the long delay! Was away!