PySandra
PySandra

Reputation: 1

How do I return wrapped C++ Objects in Cython from another wrapped Object?

I've been wrapping C++ Classes with Cython. Now I have the following problem: Class A has a method foo() which returns an instance of Class B. Both are C++ classes. Both are wrapped in Cython to Class PyA and PyB. I want to make the method foo() accessible for users in a way, that the user calls PyA_object.foo() and gets a PyB_object. How do I implement the method?

cimport cppClassA

cdef class A:
    cdef cppClassA.A *ptr
    ...
def foo():
    return self.ptr.foo()

This way I return the direct C++ Class B, but I want to return a PyB. Therefore I need to create a PyB Object, but how do I construct the cinit() method of PyB? How can I give the returned object from self.ptr.foo() to a new PyB object and return it instead?

Upvotes: 0

Views: 645

Answers (1)

DavidW
DavidW

Reputation: 30941

It isn't entirely clear from your question whether foo returns by pointer or value (i.e. whether it has the signature B* foo() or B foo()). What you have to do depends on this - I've shown both.

The problem that I think you're run into is that you the __init__ or __cinit__ method of PyB can only take Python objects as arguments, so you've got no way of passing the C++ B object in. The solution is to create a PyB that is either empty or has the default C++ object as ptr and then replace it.

The wrapping of the C++ code looks like this

cdef extern from "somefile.hpp":
    cdef cppclass A:
        B foo1()
        B* foo2()

    cdef cppclass B:
        # only need to provide these for foo1_alternative
        B()
        B(const B&)

Note that I've provided two versions of foo - foo1 which returns by value and foo2 which returns a pointer.

The Python B holder (PyB) looks like

cdef class PyB:
    cdef B* ptr

    def __cinit__(self,make_ptr=True):
        if make_ptr:
            self.ptr = new B()
        else:
            self.ptr = NULL

    def __dealloc__(self):
        del self.ptr

I've provided an optional make_ptr argument which you can set to False to avoid setting self.ptr to anything.

The Python A holder (PyA) is then

cdef class PyA:
    cdef A* ptr

    # code goes here

    def foo1(self):
        cdef PyB val = PyB()
        val.ptr[0] = self.ptr.foo1() # use move (or copy) assignment operator

    def foo1_alternative(self):
        cdef PyB val = PyB(make_ptr=False)
        val.ptr = new B(self.ptr.foo1()) # use move (or copy) constructor

    def foo2(self):
        cdef PyB val = PyB(make_ptr=False)
        val.ptr = self.ptr.foo2()

For foo2, which returns a pointer, you just create an empty PyB and assign the Foo* you get back to self.ptr. For foo1 you have two options, one of which involves creating a PyB with something in then using move (or copy) assignment operator to copy the data across. The second option involves creating an empty PyB and then using Bs move (or copy) constructor to create a B*.

You're better off using the move constructor rather than the copy constructor if you can, but you don't need to tell Cython about either (C++ will pick the right one automatically).

Upvotes: 1

Related Questions