Recent

Author Topic: Command line output to gui memo  (Read 1996 times)

davidh

  • New member
  • *
  • Posts: 9
Command line output to gui memo
« on: October 05, 2020, 08:46:02 pm »
Hi, Everyone. I know this has been kicked around for years but I have searched and searched for a succesfull outcome. I am running a commandline program which outputs both process details as it runs and creates a file and saves it to disc. I would like to take the output from the commandline and display it in a memo as it runs. I am an aging novice at this and try as I may I can't make the following work.
The program runs and creates the file and displays deatils but in the cmd window. The program then stops with this line highlighted.

 nread := Hbk.Output.Read(Buffer^, C_BUFSIZE);

and the error shown in the attached png file.

Windows 10 64bit latest Lazarus and compiler build.


This is the code I'm trying to make work

Code: Pascal  [Select][+][-]
  1. unit unit5;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Process;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     Memo1: TMemo;
  17.     procedure Button1Click(Sender: TObject);
  18.     procedure FormCreate(Sender: TObject);
  19.   //  procedure Memo1Change(Sender: TObject);
  20.   private
  21.  
  22.   public
  23.  
  24.   end;
  25.  
  26. var
  27.   Form1: TForm1;
  28.  
  29. implementation
  30.  
  31. {$R *.lfm}
  32.  
  33. { TForm1 }
  34.  
  35. procedure TForm1.Button1Click(Sender: TObject);
  36. //begin
  37.   const C_BUFSIZE = 2048;
  38. var
  39.   Hbk: TProcess;
  40.   Buffer: pointer;
  41.   SStream: TStringStream;
  42.   nread: longint;
  43. begin
  44.   Hbk := TProcess.Create(nil);
  45.  
  46.   // Replace the line below with your own command string
  47.   Hbk.CommandLine := 'C:\bin\Argyll_V2.1.2\bin\targen.exe -v -d4 -G -e8 -B8 -s8 -g8  C:\Users\David\Documents\profiles\test1';
  48.   //
  49.  
  50.   Hbk.Options := [poUsePipes];
  51.   Hbk.Options := [poStderrToOutPut];
  52.  
  53.   Hbk.Execute;
  54.  
  55.   // Prepare for capturing output
  56.   Getmem(Buffer, C_BUFSIZE);
  57.   SStream := TStringStream.Create(Nil);
  58.  
  59.   // Start capturing output
  60.   while Hbk.Running do
  61.   begin
  62.     nread := Hbk.Output.Read(Buffer^, C_BUFSIZE);
  63.     if nread = 0 then
  64.       sleep(100)
  65.     else
  66.       begin
  67.         // Translate raw input to a string
  68.         SStream.size := 0;
  69.         SStream.Write(Buffer^, nread);
  70.         // And add the raw stringdata to the memo
  71.         Memo1.Lines.Text := Memo1.Lines.Text + SStream.DataString;
  72.       end;
  73.   end;
  74.  
  75.   // Capture remainder of the output
  76.   repeat
  77.     nread := Hbk.Output.Read(Buffer^, C_BUFSIZE);
  78.     if nread > 0 then
  79.     begin
  80.       SStream.size := 0;
  81.       SStream.Write(Buffer^, nread);
  82.       Memo1.Lines.Text := Memo1.Lines.Text + SStream.Datastring;
  83.     end;
  84.   Application.ProcessMessages;
  85.   // Form1.MemoOutput.SelStart := Length(Form1.MemoOutput.Lines.Text);
  86.   until nread = 0;
  87.  
  88.   // Clean up
  89.   Hbk.Free;
  90.   Freemem(Buffer);
  91.   SStream.Free;
  92. end;
  93.  
  94. procedure TForm1.FormCreate(Sender: TObject);
  95. begin
  96.  
  97. end;
  98.  
  99. end.
  100.  
  101. procedure TForm1.Memo1Change(Sender: TObject);
  102. begin
  103.  
  104. end;
  105.  
  106. end.    

[Added code tags - see How to use the Forum.]
« Last Edit: October 06, 2020, 05:54:07 am by trev »

Edson

  • Hero Member
  • *****
  • Posts: 1301
Re: Command line output to gui memo
« Reply #1 on: October 05, 2020, 10:15:44 pm »
Hi.

Maybe you can check UnTerminal library https://github.com/t-edson/UnTerminal.

If you just want to capture the output of a process, use:

Code: Pascal  [Select][+][-]
  1.   proc:= TConsoleProc.Create(nil);
  2.   proc.RunInLoop('some_command','some_parameters', -1, outputText);
  3.   ...
  4.  

Greetings
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

bytebites

  • Hero Member
  • *****
  • Posts: 633
Re: Command line output to gui memo
« Reply #2 on: October 06, 2020, 12:48:20 am »
Replace this
Code: Pascal  [Select][+][-]
  1.   Hbk.Options := [poUsePipes];
  2.   Hbk.Options := [poStderrToOutPut];
with this
Code: Pascal  [Select][+][-]
  1.   Hbk.Options := [poUsePipes, poStderrToOutPut];

Jurassic Pork

  • Hero Member
  • *****
  • Posts: 1228
Re: Command line output to gui memo
« Reply #3 on: October 06, 2020, 01:44:15 am »
hello,
TProcessEx class in the unit ProcessUtils of fpcUpDeluxe project includes Real time output management.
see here for an example.
Friendly, J.P
Jurassic computer : Sinclair ZX81 - Zilog Z80A à 3,25 MHz - RAM 1 Ko - ROM 8 Ko

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Command line output to gui memo
« Reply #4 on: October 06, 2020, 10:05:16 am »
In FPC 3.2.0 there is a similar callback.

davidh

  • New member
  • *
  • Posts: 9
Re: Command line output to gui memo
« Reply #5 on: October 06, 2020, 03:10:33 pm »
Thanks for your help fellows. I made the minor change bytebites suggested and BOOM worked perfectly, many thanks BUT the output from the command line process appears all at once as the process finishes whereas I would like it to be displayed in real time in the memo. I'm looking at the suggestion from Jurassic Pork  but I'm getting the feeling that what I need is not possible. Anyway I'll keep at it  :)
Again, many thanks to you all

David

Edson

  • Hero Member
  • *****
  • Posts: 1301
Re: Command line output to gui memo
« Reply #6 on: October 06, 2020, 03:46:53 pm »
Thanks for your help fellows. I made the minor change bytebites suggested and BOOM worked perfectly, many thanks BUT the output from the command line process appears all at once as the process finishes whereas I would like it to be displayed in real time in the memo.

That's for what UnTerminal library was designed.

In that case you have until 4 methods for capturing the output of a process. Check documentation.

To make it easy I suggest to use the method to capture "Line by line". For that you just need to use the event OnLineCompleted(), before of start the process:

Code: Pascal  [Select][+][-]
  1. ...
  2.   proc.OnLineCompleted:=@procOnLineCompleted;
  3. ...
  4.  

Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

Zvoni

  • Hero Member
  • *****
  • Posts: 2319
Re: Command line output to gui memo
« Reply #7 on: October 07, 2020, 08:25:47 am »
Thanks for your help fellows. I made the minor change bytebites suggested and BOOM worked perfectly, many thanks BUT the output from the command line process appears all at once as the process finishes whereas I would like it to be displayed in real time in the memo. I'm looking at the suggestion from Jurassic Pork  but I'm getting the feeling that what I need is not possible. Anyway I'll keep at it  :)
Again, many thanks to you all

David

It does work, but i would have to look up, how i solved it..... Patience (@work now)
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

MarkMLl

  • Hero Member
  • *****
  • Posts: 6676
Re: Command line output to gui memo
« Reply #8 on: October 07, 2020, 11:00:20 am »
This is the guts of code that works on Linux. Broadly speaking, it is good enough to run a shell interactively which includes running a nested copy of the Lazarus IDE so that nested programs can be debugged.

Code: [Select]
...
    process.Options := [poUsePipes];
    process.Execute;
    Sleep(100);
    while process.Running or not ((process.Output.NumBytesAvailable = 0) and
                                        (process.StdErr.NumBytesAvailable = 0)) do
      if WorkerThread.Keypressed() then begin
        buffer := WorkerThread.ReadKey();
        process.Input.Write(buffer[1], 1)
      end else begin
        count := process.Output.NumBytesAvailable;
        if count > 255 then
          count := 255;
        if count > 0 then begin
          SetLength(buffer, count);
          process.Output.Read(buffer[1], count);

(* The necessity of expanding LF to CRLF (\n to \r\n) might depend on the       *)
(* terminal type, specified above to be "dumb".                                 *)

          buffer := AnsiReplaceStr(buffer, #$0a, #$0d + #$0a);
          WorkerThread.WriteWindowed(buffer)
        end else begin
          count := process.StdErr.NumBytesAvailable;
          if count > 255 then
            count := 255;
          if count > 0 then begin
            SetLength(buffer, count);
            process.StdErr.Read(buffer[1], count);
            buffer := AnsiReplaceStr(buffer, #$0a, #$0d + #$0a);
            WorkerThread.WriteWindowed(buffer)
          end else
            Sleep(10)
        end
      end

You can ignore the references to a different thread, they're only there since this is actually being run by a background thread with GUI interaction via Synchronize(). Obviously the specific I/O routines are app-specific as well, but this should be a reasonable demo of how to interact with the TProcess etc.

MarkMLl
« Last Edit: October 07, 2020, 11:04:09 am by MarkMLl »
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

davidh

  • New member
  • *
  • Posts: 9
Re: Command line output to gui memo
« Reply #9 on: October 09, 2020, 12:34:51 pm »
Thanks for your help fellows. I made the minor change bytebites suggested and BOOM worked perfectly, many thanks BUT the output from the command line process appears all at once as the process finishes whereas I would like it to be displayed in real time in the memo.

That's for what UnTerminal library was designed.

In that case you have until 4 methods for capturing the output of a process. Check documentation.

To make it easy I suggest to use the method to capture "Line by line". For that you just need to use the event OnLineCompleted(), before of start the process:

Code: Pascal  [Select][+][-]
  1. ...
  2.   proc.OnLineCompleted:=@procOnLineCompleted;
  3. ...
  4.  

Hi, thanks for your help. There is a problem with the output though. It seems to be a teletype kind of output which fills a line  then overflows onto the next line etc. (ie unformatted) whereas the out put I get with the method I was using printed out the text exactly as the cmd program produced it. I will continue though until I get it right :)

Best
David

Edson

  • Hero Member
  • *****
  • Posts: 1301
Re: Command line output to gui memo
« Reply #10 on: October 09, 2020, 04:54:03 pm »
That's for what UnTerminal library was designed.

In that case you have until 4 methods for capturing the output of a process. Check documentation.

To make it easy I suggest to use the method to capture "Line by line". For that you just need to use the event OnLineCompleted(), before of start the process:

Code: Pascal  [Select][+][-]
  1. ...
  2.   proc.OnLineCompleted:=@procOnLineCompleted;
  3. ...
  4.  

Hi, thanks for your help. There is a problem with the output though. It seems to be a teletype kind of output which fills a line  then overflows onto the next line etc. (ie unformatted) whereas the out put I get with the method I was using printed out the text exactly as the cmd program produced it. I will continue though until I get it right :)

Best
David

That's because I suggest you the more simple method to capture Output.

"UnTerminal" have more advanced method to capture output including the recognition of some VT100 sequences:

The next method, improve the capture, printing partial lines:

Code: Pascal  [Select][+][-]
  1. var LinPartial: boolean = false;
  2.  
  3.   proc.OnLineCompleted:=@procLineCompleted;
  4.   proc.OnReadData:=@procReadData;
  5. ...
  6.  
  7. procedure TForm1.procLineCompleted(const lin: string);
  8. begin
  9.   if LinPartial then begin
  10.     Memo1.Lines[Memo1.Lines.Count-1] := lin;  
  11.     LinPartial := false;
  12.   end else begin  
  13.     Memo1.Lines.Add(lin);
  14.   end;
  15. end;
  16.  
  17. procedure TForm1.procReadData(nDat: integer; const lastLin: string);
  18. begin
  19.   LinPartial := true;  
  20.   Memo1.Lines.Add(lastLin);
  21. end;
  22.  

Ypu can find it, used in the sample projects.
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

davidh

  • New member
  • *
  • Posts: 9
Re: Command line output to gui memo
« Reply #11 on: November 24, 2020, 03:28:30 pm »
Thank you all for your help. It has got me a long way toward understanding. The problem is still there I'm afraid, probably because of my lack of understanding. Another problem is that while the command line program is running, the whole thing pauses (even a 'Loading' gif). If my thinking is correct (?) the buffer doesn't release it's content until the process is finished so I think what I need is some way to out put directly (as soon as it sees a \n etc.)

Thanks again everyone and keep safe :)

David

Edson

  • Hero Member
  • *****
  • Posts: 1301
Re: Command line output to gui memo
« Reply #12 on: November 24, 2020, 06:39:57 pm »
Another problem is that while the command line program is running, the whole thing pauses (even a 'Loading' gif). If my thinking is correct (?) the buffer doesn't release it's content until the process is finished so I think what I need is some way to out put directly (as soon as it sees a \n etc.)

I think you should show some code in order to make clear the problem.
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

davidh

  • New member
  • *
  • Posts: 9
Re: Command line output to gui memo
« Reply #13 on: November 24, 2020, 07:06:41 pm »
Hi,
This is the code I'm using. I can't pretend that it's mine and it works. Problem is that it produces output to my memo at the conclusion or the process. I have looked at edsons code but the output is a little untidy. I've tried both of his suggestions but I cannot make them work

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button3Click(Sender: TObject);
  2.     const C_BUFSIZE = 2048;
  3.     label start;
  4. var
  5.   Hbk: TProcess;
  6.   Buffer: pointer;
  7.   SStream: TStringStream;
  8.   nread: longint;
  9.   ButtonPress : boolean;
  10. begin
  11.                             //check file exists
  12.     if FileExists(EditBaseDirectory.Text + ProfNameEdit.Text + '.ti1') then
  13.                                 begin
  14.                                    Form2.PassedData := ProfNameEdit.Text;
  15.                                    if Form2.ShowModal = mrNo then
  16.                                      Exit
  17.                                    else
  18.                                       ///overwrite
  19.                                         end;
  20.  Hbk := TProcess.Create(nil);
  21.     SetCurrentDir(Form1.EditBaseDirectory.Text);
  22.  Hbk.CommandLine := Form1.EditCommandLine.Text;
  23.  Hbk.Options := [poUsePipes,poStderrToOutPut,poNoConsole];
  24.  Hbk.Execute;
  25.  // Prepare for capturing output
  26.   Getmem(Buffer, C_BUFSIZE);
  27.   SStream := TStringStream.Create(Nil);
  28. // Start capturing output
  29.   while Hbk.Running do
  30.   begin
  31.      nread := Hbk.Output.Read(Buffer^ , C_BUFSIZE);
  32.      if nread = 0 then
  33.        sleep(10)
  34.     else
  35.       begin
  36.         // Translate raw input to a string
  37.         SStream.size := 0;
  38.         SStream.Write(Buffer^, nread);
  39.         // And add the raw stringdata to the memo
  40.         Memo1.Lines.Text := Memo1.Lines.Text + SStream.DataString;
  41.       end;
  42.   end;
  43.  
  44.   // Capture remainder of the output
  45.   repeat
  46.     nread := Hbk.Output.Read(Buffer^, C_BUFSIZE);
  47.     if nread > 0 then
  48.       SStream.size := 0;
  49.       SStream.Write(Buffer^, nread);
  50.       Memo1.Lines.Text := Memo1.Lines.Text + SStream.Datastring;
  51.       Application.ProcessMessages;
  52.       Memo1.SelStart := Length(Memo1.Lines.Text);
  53.   until nread = 0;
  54.       Hbk.Free;
  55.       Freemem(Buffer);
  56.       SStream.Free;
  57. end;                                                          
  58.  [code=pascal]

 

TinyPortal © 2005-2018