Recent

Author Topic: Redirect program-output to TMemo in real-time  (Read 36232 times)

eny

  • Hero Member
  • *****
  • Posts: 1634
Re: Redirect program-output to TMemo in real-time
« Reply #30 on: July 15, 2012, 06:40:38 pm »
I think that's not needed - shouldn't it be sufficient to call ShellCommandRunnerThread.Terminate for stopping the hanging thread
It is needed because the thread instantiates another class: TShellCommandRunner.
And that one does some allocation that needs to be freed as well hence the extra flag to have this one finish gracefully.
All posts based on: Win10 (Win64); Lazarus 2.0.10 'stable' (x64) unless specified otherwise...

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: Redirect program-output to TMemo in real-time
« Reply #31 on: July 16, 2012, 12:31:53 pm »
Thank you for this explanation - this makes sense.

I tried to implement the TerminationRequested variable that you proposed but, as you predicted, I failed with synchronization issues resulting in memory leaks.

The other approach using FreeOnTerminate=false was more successful -- please find a demo project in the attachment. Now the thread is freed in the OnTerminate event handler, and there is a new method TShellCommandRunner.Terminate to terminate the runner's process. This works fine when called by user interaction (click on the "Terminate" button), but occasionally leaves a memory leak when the form is closed while the long process is still running.

Do you have any idea how to fix this?

eny

  • Hero Member
  • *****
  • Posts: 1634
Re: Redirect program-output to TMemo in real-time
« Reply #32 on: July 16, 2012, 08:48:42 pm »
Do you have any idea how to fix this?
First and foremost: do not access FRunner.Process directly.
It is a private property and the thread has no control over it nor does it know how to handle a graceful process termination.
As a result, every time you call Terminate you create a memory leak.

You need some changes:
1. Introduce the TerminationRequested variable and use it in the loop as I suggested earlier for TShellCommandRunner.
2. Introduce an abort method for the runner that controls this variable.
3. Introduce an abort method to the thread that you call. It calls abort method of the runner for you.
See attachment for the changes.

The one single issue that you need to resolve is to make sure the thread is not already freed when you abort it.
This can only be safely done by using a synchronization mechanism between the runner thread and the main thread (e.g. criticalsection).
But if the abort is only used at program shutdown it probably is ok (you can always put the call of the thread.abort method in a try/except block. If it fails the thread was already freed.)
« Last Edit: July 16, 2012, 10:10:14 pm by eny »
All posts based on: Win10 (Win64); Lazarus 2.0.10 'stable' (x64) unless specified otherwise...

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: Redirect program-output to TMemo in real-time
« Reply #33 on: July 16, 2012, 10:35:19 pm »
Thank you. I already had this solution, but I did not call Process.Terminate in the Runner's Execute method.

mosquito

  • Full Member
  • ***
  • Posts: 141
Re: Redirect program-output to TMemo in real-time
« Reply #34 on: December 10, 2021, 01:56:15 pm »
Try to do it with a separate thread; so you only update the memo when there actually is data available (see attachment for the class that you can use. Guarantees until the front door... ).

Example to run a command and capture the output (windows):
Code: [Select]
type
  TfrmMain1 = class(TForm)
  ...
  private
    procedure OnOutputAvailable(const pBuffer: PByteArray; const pCount: integer);
  ...

implementation

// Test button to start the threaded dir command
procedure TfrmMain1.Button1Click(Sender: TObject);
var th: TShellCommandRunnerThread;
begin
  th := TShellCommandRunnerThread.Create;
  th.CommandLine := 'cmd /c dir d:\temp /s/b';
  th.OnOutputAvailable := @OnOutputAvailable;
  th.Start;
end;

// Capture the output
procedure TfrmMain1.OnOutputAvailable(const pBuffer: PByteArray; const pCount: integer);
begin
  Memo1.Append(TShellCommandRunner.BufferToString(pBuffer,pCount));
end;

This code work perfect for me.
Using in GNU/Linux for all kind of shell commands. Lines are parsed OK, indeed with any STDOUT speed.

However, with my own programs ... have the following problem :
Capturing from simple GUI with TMemo and your threaded TProcess code...

This Work
Code: Pascal  [Select][+][-]
  1. uses
  2.   {$IFDEF UNIX}
  3.   cthreads,
  4.   {$ENDIF}
  5.   Classes,sysutils
  6.   { you can add units after this };
  7.  
  8. VAR I : INTEGER ;
  9. BEGIN
  10.  
  11.   FOR I := 0 TO 100 DO BEGIN
  12.   WRITEln('My super line for testing :  '
  13.          + INTTOSTR(I) + sLineBreak);
  14.   END;
  15.  
  16. END.
  17.  
But this not. (Lines are cut).
Code: Pascal  [Select][+][-]
  1. uses
  2.   {$IFDEF UNIX}
  3.   cthreads,
  4.   {$ENDIF}
  5.   Classes,sysutils
  6.   { you can add units after this };
  7.  
  8. VAR I : INTEGER ;
  9. BEGIN
  10.  
  11.   FOR I := 0 TO 100 DO BEGIN
  12.   WRITEln('My super line for testing :  '
  13.          + INTTOSTR(I) + sLineBreak);
  14.   SLEEP(100); // <----------- ! !
  15.   END;
  16.  
  17. END.
  18.  
Why ?

Roland57

  • Sr. Member
  • ****
  • Posts: 423
    • msegui.net
Re: Redirect program-output to TMemo in real-time
« Reply #35 on: December 10, 2021, 03:37:03 pm »
@mosquito

Have you noticed that you answer to a ten years old message?  :)

For your question, I think that the following addition should solve the problem.

Code: Pascal  [Select][+][-]
  1.   Flush(output); // <--
  2.   SLEEP(100);
My projects are on Gitlab and on Codeberg.

mosquito

  • Full Member
  • ***
  • Posts: 141
Re: Redirect program-output to TMemo in real-time
« Reply #36 on: December 10, 2021, 05:18:29 pm »
The truth is, no.
The good news is that your line works great. I'm really grateful to you. I was confused with respect to the behavior of Stream. I thought that the Chunks were controlled by EOL and READ_BLOKCSIZE = 4096;

 

TinyPortal © 2005-2018