iqrf 3 Posted April 15, 2023 (edited) Hi, I'm completely new to P4D and trying to pass a python object to delphi as a function parameter with no success. Python: import delphi class myclass: def __init__(self, x, y): self.x = x + y def increment(self): self.x += 1 def get_x(self): return self.x if __name__ == '__main__': testC = myclass(1, 2) testC.increment() print(testC.get_x()) test = delphi.test test.send(testC) # raised exception class EPyTypeError with message 'TypeError: "Send" called with invalid arguments. Error: Could not find a method with compatible arguments'. Delphi: {$METHODINFO ON} TTest = class(TPersistent) private public procedure Send(AObject: PyObject); published end; {$METHODINFO OFF} procedure TTest.Send(AObject: PyObject); begin //I don't know how to get the x value of myclass here //showmessage(x.toString); end; ... ... var test: TTest; p: PPyObject; begin test := TTest.Create; p := PyDelphiWrapper.Wrap(test, soOwned); PythonModule.SetVar('test', p ); GetPythonEngine.ExecString(UTF8Encode(sePythonCode.Text)); end; Many thanks ahead. Petr Edited April 15, 2023 by iqrf Share this post Link to post
KoRiF 1 Posted April 15, 2023 (edited) This stuff may be a bit less straightforward: https://github.com/pyscripter/python4delphi/blob/master/Tutorials/Webinar I/ModuleDemo/MainForm.pas#L126 Please check this: Webinar Hope this helps. P.S. this can clear rather odd arg parsing syntax: https://docs.python.org/3/c-api/arg.html Edited April 15, 2023 by KoRiF Share this post Link to post
iqrf 3 Posted April 16, 2023 Yes it can be done using events or just like this test.send(testC.get_x()) procedure Send(Data: Variant); My point is to pass a python object and then call its function in Delphi. Share this post Link to post
pyscripter 694 Posted April 16, 2023 (edited) On the Delphi side you can use PyArg_ParseTuple to get the pointer to the python object. Then you can either: use the python API to call the desired method on the python object or wrap the python object into a custom variant (VarPythonCreate in the VarPyth unit) and call the method in a high-level way. See tutorials and demos on VarPyth for details Edited April 16, 2023 by pyscripter 1 Share this post Link to post
iqrf 3 Posted April 19, 2023 HI, I took inspiration from Demo34 and did what I needed. Everything works. I just want to ask why it is necessary to call Adjust(@Self); Do I have to worry about releasing the created PyBytes in TPyPoint.DoReceive using Py_XDECREF(pyBytes) or will Python take care of that? Thanks function TPyPoint.DoSend(args : PPyObject) : PPyObject; var data: TBytes; pyBytes: PPyObject; function PyBytesAsBytes(Obj: PPyObject): TBytes; var size: NativeInt; buffer: PAnsiChar; arr: TBytes; begin Result := Default(TBytes); with GetPythonEngine do begin if not PyBytes_Check(Obj) then Exit; PyBytes_AsStringAndSize(Obj, buffer, size); if (size = 0) then Exit; SetLength(Result, size); CopyMemory(Result, buffer, size); end; end; begin with GetPythonEngine do begin Adjust(@Self); if PyArg_ParseTuple( args, 'S:Point.Send', @pyBytes ) <> 0 then begin data := PyBytesAsBytes(pyBytes); // .... Result := ReturnNone; end else Result := nil; end; end; function TPyPoint.DoReceive(args : PPyObject) : PPyObject; var pyBytes: PPyObject; data: TBytes; begin with GetPythonEngine do begin Adjust(@Self); SetLength(data, 5); data[0] := 65; data[1] := 66; data[2] := 67; data[3] := 68; data[4] := 69; PyBytes := PyBytes_FromStringAndSize(PAnsiChar(data), Length(data)); Result := pyBytes; end; end; import spam d = bytes([0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6]) spam.myPoint.Send(d) print(list(d)) d = spam.myPoint.Receive() print(list(d)) Share this post Link to post
Remy Lebeau 1436 Posted April 19, 2023 (edited) 13 hours ago, iqrf said: Everything works. I just want to ask why it is necessary to call Adjust(@Self); There is a description in the PythonEngine source code: { A B C +-------------------++------------------------------------------------------+ | PyObject header || TPyObject class | +----------+--------++-----------------+------------+----------+------------+ |ob_refcnt |ob_type ||hidden Class Ptr |PythonType |IsSubType |PythonAlloc | |integer |pointer ||pointer |TPythonType |Boolean |Boolean | |4 bytes |4 bytes ||4 bytes |4 bytes |1 byte |1 byte | +----------+--------++-----------------+------------+----------+------------+ ^ ^ | | ptr returned ptr returned by Adjust by GetSelf - a Python object must start at A. - a Delphi class class must start at B - TPyObject.InstanceSize will return C-B - Sizeof(TPyObject) will return C-B - The total memory allocated for a TPyObject instance will be C-A, even if its InstanceSize is C-B. - When turning a Python object pointer into a Delphi instance pointer, PythonToDelphi will offset the pointer from A to B. - When turning a Delphi instance into a Python object pointer, GetSelf will offset Self from B to A. - Properties ob_refcnt and ob_type will call GetSelf to access their data. } Internally, Adjust() calls PythonToDelphi(): procedure TPyObject.Adjust(PyPointer: Pointer); var ptr : PNativeInt; begin ptr := PyPointer; ptr^ := NativeInt(PythonToDelphi(PPyObject(ptr^))); end; function PythonToDelphi( obj : PPyObject ) : TPyObject; begin if IsDelphiObject( obj ) then Result := TPyObject(PAnsiChar(obj)+Sizeof(PyObject)) else raise EPythonError.CreateFmt( 'Python object "%s" is not a Delphi class', [GetPythonEngine.PyObjectAsString(obj)] ); end; Quote Do I have to worry about releasing the created PyBytes in TPyPoint.DoReceive using Py_XDECREF(pyBytes) or will Python take care of that? I have no idea. Edited April 19, 2023 by Remy Lebeau Share this post Link to post
pyscripter 694 Posted April 19, 2023 (edited) 13 hours ago, iqrf said: Do I have to worry about releasing the created PyBytes in TPyPoint.DoReceive using Py_XDECREF(pyBytes) No. You should try to understand the rules behind reference counting. (e.g. Reference Counting in Python (tripod.com)) Quote Most functions INCREF any object that should continue to exist after the function exits. This includes objects returned via arguments or a return statement. In these cases the calling function usually has responsibility for calling Py_DECREF(). The calling function can, in turn, pass responsibility for the DECREF to its caller. Note that PyBytes_FromStringAndSize returns a new reference, so there is no need to increment the count. Adjust(Self) is needed when you need to access the Delphi object properties or methods. Edited April 19, 2023 by pyscripter Share this post Link to post
iqrf 3 Posted April 20, 2023 7 hours ago, pyscripter said: (e.g. Reference Counting in Python (tripod.com)) The a great document, thanks a lot. Share this post Link to post