Jump to content

pyscripter

Members
  • Content Count

    1009
  • Joined

  • Last visited

  • Days Won

    66

Posts posted by pyscripter


  1. The PythonVersions unit is of little value in Linux, where there is no Registry and no registered Python versions.  You need to manually provide the DLLName and DLLPath.

     

    You can use some code that searches possible file locations.  For example in the Mac you can do, something like:

    {$ifdef MACOS}

    for N:= 5 to 9 do begin

      S:= Format('/Library/Frameworks/Python.framework/Versions/3.%d/lib/libpython3.%d.dylib', [N, N]);

      if FileExists(S)

        then exit(S);

    end;

    {$endif}

     

    In Debian Linux you can search for file locations such as:

    /usr/lib/x86_64-linux-gnu/libpython3.7m.so
    /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1
    /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0

     

    but it may vary by python distribution.

    • Like 1

  2. On 3/10/2021 at 5:55 PM, J. Robroeks said:

    Thank you for your quick and elaborate answer. The py_subject was indeed freed at the wrong place. 

     

    In case someone else wonders the following:

    There are several funtions in Python that are non-blocking. For example:

    1. sleep
    2. urlopen

     

    None of the python code is blocking other threads.  But the following functions are blocking:

    • PyGILState_Ensure
    • PyEval_RestoreThread

    In other words you need to get and hold to the GIL to execute any python code.  So take for example sleep.  It works like windows sleep.   But to let other threads to execute python code you need to use Py_Begin_Allow_Threads /Py_End_Allow_Threads   before/after the sleep.  Python deals with the threads it creates.  (threading module).


  3. In your code above you should destroy PY_SUBJECT before releasing the GIL

    Python has a lesser known feature called sub-interpreters, which allows you to use the interpreter from a clean state.  This is what emNewInterpreter does.  Normally I would not bother with that.

     

    The pattern you need to follow using the latest version of P4D:

     

    In your main thread to release the GIL after loading the Python dll:  

    TPythonThread.Py_Begin_Allow_Threads  (calls PyEval_SaveThread)

     

    In your Delphi threads including the main thread (or if you use ITask or  TParallel) that execute python code (this is what TPythonThread does):

      fGILState := .PyGILState_Ensure;
      try
        Do python staff
      finally
         PyGILState_Release(fGILState);
      end;

     

    In your main thread before unlolading Python

    TPythonThread.Py_End_Allow_Threads  (calls PyEval_RestoreThread)

     

    if you have a long running thread that does python stuff and you want allow other threads to do python stuff as well then the pattern is:

     

      fGILState := .PyGILState_Ensure;
      try
        Do python staff
        TPythonThread.Begin_Allow_Threads;
        try
          Other threads can run python code
        finally
          TPythonThread.End_ALlow_Threads;
        end;
        Do more python stuff.
      finally
         PyGILState_Release(fGILState);
      end;
    

     

    In PyScripter I have a utility function:

    type
      IPyEngineAndGIL = interface
        function GetPyEngine: TPythonEngine;
        function GetThreadState: PPyThreadState;
        property PythonEngine: TPythonEngine read GetPyEngine;
        property ThreadState: PPyThreadState read GetThreadState;
      end;
    
    function SafePyEngine: IPyEngineAndGIL;
    begin
      Result := TPyEngineAndGIL.Create
    end;
    
    
    type
      TPyEngineAndGIL = class(TInterfacedObject, IPyEngineAndGIL)
      fPythonEngine: TPythonEngine;
      fThreadState: PPyThreadState;
      fGILState: PyGILstate_STATE;
      private
        function GetPyEngine: TPythonEngine;
        function GetThreadState: PPyThreadState;
      public
        constructor Create;
        destructor Destroy; override;
      end;
    
    { TPyEngineAndGIL }
    
    constructor TPyEngineAndGIL.Create;
    begin
      inherited Create;
      fPythonEngine := GetPythonEngine;
      fGILState := fPythonEngine.PyGILState_Ensure;
      fThreadState := fPythonEngine.PyThreadState_Get;
    end;
    
    destructor TPyEngineAndGIL.Destroy;
    begin
      fPythonEngine.PyGILState_Release(fGILState);
      inherited;
    end;
    
    function TPyEngineAndGIL.GetPyEngine: TPythonEngine;
    begin
      Result := fPythonEngine;
    end;
    
    function TPyEngineAndGIL.GetThreadState: PPyThreadState;
    begin
      Result := fThreadState;
    end;

    which is used in the main or other threads as:

    var Py: IPyEngineAndGIL;

    begin

       Py := SafePyEngine;

       Py.Engine.

     

    whenever I need to execute Python code

    • Like 1

  4. 9 hours ago, balabuev said:

    I've replaced the treeview with a frame to remove the mess with the second pair of unneeded scrollbar windows. I've simulated some resource allocation on WM_CREATE in a frame, which is then freed on WM_DESTROY, similar to tree view.

     

    I guess the issue would still be present if you now replace the ListView with anything that has scrollbars including a treeview.  Is that right?


  5. How about this one?

     

    class procedure TStyleEngine.DoRemoveControl(Control: TWinControl);
    begin
      if not (csDestroying in Control.ComponentState) and (FControls <> nil) and FControls.ContainsKey(Control) then
      begin
        var Hook := FControls.Items[Control];
        TThread.ForceQueue(nil, procedure
        begin
          Hook.Free;
        end);
        FControls.Remove(Control);
      end;
    end;

    There may be more efficient solutions, such as adding the hooks for deletion to a List and process that list on idle time or on Hook creation.

     

    • Like 1

  6. In light of @David Heffernan remarks here is a shorter version of @Stephen Ball code:


     

    type
      TForm1 = class(TForm)
        procedure CreateWnd; override;
        procedure WMWTSSessionChange (var Message: TMessage); message WM_WTSSESSION_CHANGE ;
        procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY;
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm1.WMDestroy(var Message: TWMDestroy);
    begin
      inherited;
      WTSUnRegisterSessionNotification(Handle);
    end;
    
    procedure TForm1.WMWTSSessionChange(var Message: TMessage);
    begin
      case Message.wParam of
        WTS_SESSION_LOCK,
        WTS_REMOTE_DISCONNECT: Application.UpdateMetricSettings := False;
        WTS_REMOTE_CONNECT,
        WTS_SESSION_UNLOCK:
          TThread.ForceQueue(nil, procedure
          begin
            Application.UpdateMetricSettings := True;
          end, 30000);
      end;
    end;
    
    procedure TForm1.CreateWnd;
    begin
      inherited;
      WTSRegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION);
    end;

    Instead of CreateWnd you could override the CreateWindowHandle method.


  7. 36 minutes ago, David Heffernan said:

    My answer to this old SO question presents a different way that may be simpler in the long run. 

    Thanks!  DestroyWnd is not called when the application exits.  Is this an issue here?   You could call the WTSUnRegisterSessionNotification at the WM_DESTROY handler instead. 


  8. 2 hours ago, Stephen Ball said:

    As @David Millington mentioned, we have been working with the R&D team to share tips learned to help speed up VCL applications under remote desktop - to read about it, including a code sample see:

    https://blogs.embarcadero.com/how-to-speed-up-remote-desktop-applications/ 

    @Stephen BallThank you very much!

     

    Is there a need to have a separate window to receive the notifications?   Isn't the code below equivalent to yours?

    type
      TForm1 = class(TForm)
        procedure FormDestroy(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure WMWTSSessionChange (var Message: TMessage); message WM_WTSSESSION_CHANGE ;
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    var
      Form1: TForm1;
    implementation
    {$R *.dfm}
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
        WTSUnRegisterSessionNotification(Handle);
    end;
    
    procedure TForm1.WMWTSSessionChange(var Message: TMessage);
    begin
      inherited;
      case Message.wParam of
        WTS_SESSION_LOCK,
        WTS_REMOTE_DISCONNECT: Application.UpdateMetricSettings := False;
        WTS_REMOTE_CONNECT,
        WTS_SESSION_UNLOCK:
          TThread.ForceQueue(nil, procedure
          begin
            Application.UpdateMetricSettings := True;
          end, 30000);
      end;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      WTSRegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION);
    end;

     


  9. 11 minutes ago, balabuev said:

    First one will be deleted by window manager, but the second - not.

    This is not true.  StyleEngine.Destroys calls FreeControlHooks which destroys all remaining hooks.   However what @Attila Kovacs suggests is problematic, since you keep accumulating unreleased hooks if the application creates and destroys styled controls.

    • Like 1

  10. I did a bit of research to assess whether other parts of Vcl could be suffering from the same issue.  The only controls that recreate themselves in response to the CM_STYLECHANGED message are the TListView and the ActionManager controls (ActionToolbar etc.).  I think there are not scrollbars and the like there, but I wonder why there is a need to do that.  So the issue may be confined to the ListView and if the call to RecreateWnd can be avoided that would be the simplest solution.


  11. Update:  This does not work well.:classic_blush:

     

    One suggestion I have is the following:

     

    Change TStyleEngine.DoRemoveControl(Control: TWinControl) from:

     

    class procedure TStyleEngine.DoRemoveControl(Control: TWinControl);
    var
      I: Integer;
      LControl: TControl;
    begin
      if (FControls <> nil) and FControls.ContainsKey(Control) then
      begin
        for I := 0 to Control.ControlCount - 1 do
        begin
          LControl := Control.Controls[I];
          if LControl is TWinControl then
            DoRemoveControl(TWinControl(LControl));
        end;
        FControls.Items[Control].Free;
        FControls.Remove(Control);
      end;
    end;

    to

    class procedure TStyleEngine.DoRemoveControl(Control: TWinControl);
    var
      I: Integer;
      LControl: TControl;
    begin
      if not (csRecreating in Control.ControlState) and (FControls <> nil) and FControls.ContainsKey(Control) then
      begin
        for I := 0 to Control.ControlCount - 1 do
        begin
          LControl := Control.Controls[I];
          if LControl is TWinControl then
            DoRemoveControl(TWinControl(LControl));
        end;
        FControls.Items[Control].Free;
        FControls.Remove(Control);
      end;
    end;

    or even to:

    class procedure TStyleEngine.DoRemoveControl(Control: TWinControl);
    begin
      if not (csRecreating in Control.ControlState) and (FControls <> nil) and FControls.ContainsKey(Control) then
      begin
        FControls.Items[Control].Free;
        FControls.Remove(Control);
      end;
    end;

    There seems to be no reason to destroy the hook if the control is recreating. Also hooks of child controls would be removed when they get destroyed, so there is no need to do this in advance.

    Do you see anything wrong with the above?  With the above change the test app runs fine and without memory leaks.  And the change would save a lot of hook destruction and reconstruction.

     

    To test copy Vcl.Styles.pas, StyleAPI.inc and StyleUtils.inc to the project source directory, make the change and re-build the project.

     


  12. 6 hours ago, balabuev said:

    Part 2:

     

    The demo with simple three buttons on a panel.

    • RunTestClick destroys the panel handle using DestroyWindow function.
    • Button1 is the first in Z-order, and thats why it will first receive WM_DESTROY message. In the message handler it will destroy Button2 recursively.
    • And the test shows, that in this case Button3 will not receive WM_DESTROY.

    ButtonsTest.zip

     

     

    We now understand the cause of this issue.  Great work @balabuev.  But now we need to come up with a good fix to propose to Embarcadero. 

     

    My earlier question "Shouldn't the ListView TScrollWindows be above the ListView after it gets recreated?" still stands.  If the order was not messed up, the error would not occur.   Or can the recursive calling of DestroyWindow window be avoided without introducing other bugs?

     

    Although in the case of the ListView the RecreateWnd as a response ot CM_STYLECHANGED the call to RecreateWnd appears to be redundant, RecreateWnd happens often with many controls and for all sort of reasons (e.g. a property change).  So Vcl should be able to handle such calls robustly.

     


  13. 1 hour ago, balabuev said:

    Given the following panel child order:

    Shouldn't the ListView TScrollWindows be above the ListView after it gets recreated? This is what happens when it is originally created Why are they not on top?

     

    By the way readers of this thread may be interested in a related old post of mine: 

     


  14. 43 minutes ago, Dalija Prasnikar said:

    VCL Styles were never mature, they are just feature that was never properly finished in the first place.

    Although Vcl.Styles is not mature.  I would say it is usable.   The Delphi IDE is a proof of that.   I have been producing PyScripter with Vcl.Styles for the last five years, and I had no issues that could not be overcome.   And now you can have per monitor aware, styled Delphi applications.

    And since this this discussion started about flicker, there are ways of working around that.  The Delphi IDE and PyScripter have minimal flicker.

     

    The great thing about Vcl is that applications developed almost 20 years ago, can still work well and look modern,  just by recompiling pretty much the same old code.  I have mentioned this before, just compare to the Microsoft Desktop application landscape.  A new framework is introduced every 3-4 years, mostly incompatible with the others:

    - Visual Basic with ActiveX controls

    - C++ with Windows API

    - WinForms

    - WPF

    - Silverlight

    - Xamarin

    - UWP

    - WinUI

    - MAUI

    - Project Reunion

    - etc.

     

    Where would you be if you invested in Visual Basic and ActiveX controls for example?  Ironically, you can still use ActiveX controls in Delphi if you so wish.

     

     

    • Like 3
×