Jump to content
iqrf

Passing a python object to a delphi function parameter

Recommended Posts

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 by iqrf

Share this post


Link to post

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

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 by pyscripter
  • Thanks 1

Share this post


Link to post

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
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 by Remy Lebeau

Share this post


Link to post
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 by pyscripter

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×