marcelsema 0 Posted 6 hours ago Hello everyone, I am looking to destroy an object once it is no longer referenced in python. I am struggeling to correctly do this. unit Unit1; interface uses SysUtils, Classes, Windows, Messages, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls, PythonEngine, Vcl.PythonGUIInputOutput, WrapDelphi; type TForm1 = class(TForm) Splitter1: TSplitter; Memo1: TMemo; PythonEngine1: TPythonEngine; PythonModule1: TPythonModule; Panel1: TPanel; Button1: TButton; PythonGUIInputOutput1: TPythonGUIInputOutput; Memo2: TMemo; PyDelphiWrapper1: TPyDelphiWrapper; procedure Button1Click(Sender: TObject); private public destructor Destroy; override; function GetDelphiWrapper: TPyDelphiWrapper; function GetPythonEngine: TPythonEngine; end; TEntry = class(TObject) strict private FstrName: string; FclsStringList: TStringList; public constructor Create(strName: string); destructor Destory; function Name: string; end; TManager = class(TObject) public function GetNewEntry: PPyObject; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var clsManager: TManager; pyManager: PPyObject; begin clsManager := TManager.Create; pyManager := PyDelphiWrapper1.Wrap(clsManager, soReference); PythonModule1.SetVar( 'manager', pyManager ); PythonEngine1.ExecStrings( memo1.Lines ); PythonEngine1.Py_DecRef(pyManager); end; { TManager } function TManager.GetNewEntry: PPyObject; begin // I want TEntry to be destroyed by python once it is no longer used Result := Form1.GetDelphiWrapper.Wrap(TEntry.Create('Test'), soOwned); Form1.GetPythonEngine.Py_DECREF(Result); end; destructor TForm1.Destroy; begin FreeAndNil(PythonModule1); FreeAndNil(PythonEngine1); inherited; end; function TForm1.GetDelphiWrapper: TPyDelphiWrapper; begin Result := PyDelphiWrapper1; end; function TForm1.GetPythonEngine: TPythonEngine; begin Result := PythonEngine1; end; { TEntry } constructor TEntry.Create(strName: string); begin inherited Create; FclsStringList := TStringList.Create; FstrName := strName; end; destructor TEntry.Destory; begin FreeAndNil(FclsStringList); inherited; end; function TEntry.Name: string; begin Result := FstrName; end; end. from spam import * entry = manager.GetNewEntry() print(entry.Name()) After clicking the button, I get an exception accessing an invalid pointer. I also tried returning the object directly instead of wraping it to a PPyObject, but that did not work. Thank you for your help. Best regards, Marcel Share this post Link to post
pyscripter 796 Posted 3 hours ago (edited) function TManager.GetNewEntry: PPyObject; begin // I want TEntry to be destroyed by python once it is no longer used Result := Form1.GetDelphiWrapper.Wrap(TEntry.Create('Test'), soOwned); Form1.GetPythonEngine.Py_DECREF(Result); end; In the code above Py_DECREF will destroy the TEntry instance immediately, You can confirm that by debugging and putting a brekpoint at TEntry.Destroy. Just remove the Py_DECREF statement. Explanation.: Wrap returns a python object with reference count of 1. When you decrease the reference count, it goes down to 0, and because the wrapped Delphi object is owned it gets destroyed The difference with clsManager := TManager.Create; pyManager := PyDelphiWrapper1.Wrap(clsManager, soReference); PythonModule1.SetVar( 'manager', pyManager ); PythonEngine1.ExecStrings( memo1.Lines ); PythonEngine1.Py_DecRef(pyManager); is that you call SetVar which increases the reference count. However the above code leaks clsManager. It does not get destroyed because you set the Ownership to soReference. Either change that or call clsManager.Free at the end. Conclusion: Python reference counting can be tricky. You should try to understand the rules behind reference counting. (e.g. Reference Counting in Python (tripod.com)) if you use the Python API directly. High level P4D modules (WrapDelphi and VarPython) to a large extent insulate you from that and ensure correct use of reference counting. Edited 2 hours ago by pyscripter Share this post Link to post