Calling Cython from c++ with not built-in types

2019-07-17 04:50发布

问题:

i'm trying to extend functionality of my lib written in C++ with Python and Cython. I have class MyClass in C++ wich is essential for my lib. And i'm using a lot it's wrapper class PyMyClass in Python. So i want to use functions (with PyMyClass as argument) from C++. How i can accomplish this?

I imagine it something like this:

cdef public my_func(MyClass class):
     *cast MyClass to PyMyClass*
     other_py_func(PyMyClass)

myclass.h

namespace classes {
    class MyClass
    {

    public:
        explicit MyClass(int x, int y);
        int sum();
    private:
        int a;
        int b;
    };
}

myclass.cpp

MyClass::MyClass(int x, int y)
{
    a = x;
    b = y;
}

MyClass::sum()
{
    return a + b
}

pymyclass.pyx

cdef extern from "myclass.h" namespace "classes":
    cdef cppclass MyClass:
        MyClass(int x, int y) except +
        int sum()

    cdef public my_func(MyClass var):
         print "It is python"

cdef class PyMyClass(object): cdef MyClass *c_class

    def __cinit__(self, int x, int y):
        self.c_class = new MyClass(x,y)

   def __dealoc__(self):
       del self.c_class.sum()

    def sum(self):
        return self.c_class.sum()

I understand that i can add get_x(), get_y() and set_x(int), set_y(int) to MyClass and just copy all the fields from MyClass to PyMyClass. But PyMyClass has already a pointer to MyClass. Is it possible just assign address of MyClass instance to PyMyClass field? I mean something like this:

cdef class PyMyClass(object):
       cdef MyClass *c_class

       def __cinit__(self, int x, int y):
            self.c_class = MyClass(x,y)

       def __cinit__(self, void * ptr):
           self.c_class = (Antenna *)ptr

       def __dealoc__(self):
           del self.c_class.sum()

       def sum(self):
            return self.c_class.sum()

Then Cython function will look like cdef public my_func(MyClass *class): pyclass = PyMyClass(class) other_py_func(pyclass)

回答1:

To create a Cython extension class from existing C++ pointer, use a factory function.

For example:

cdef class PyMyClass:
   cdef MyClass *c_class

   def __cinit__(self):
        # Set pointer to null on object init
        self.c_class = NULL

   def __dealoc__(self):
       if self.c_class is not NULL:
           del self.c_class

   def sum(self):
        return self.c_class.sum()

cdef object PyMyClass_factory(MyClass *ptr):
    cdef PyMyClass py_obj = PyMyClass()
    # Set extension pointer to existing C++ class ptr
    py_obj.c_class = ptr
    return py_obj

That gives you access to the C++ class via its pointer without copying or moving any data. PyMyClass_factory can be used anywhere you want to return a Python PyMyClass object from an existing C++ pointer.

Note that Python does not support function overloading (nor Cython) so you cannot have two __cinit__ signatures, one with no arguments and one that creates a new C++ class from two integers.

If you want to create a new C++ class via Cython, a new factory function to do that is needed as per above example.

It would look like:

cdef object PyMyClass_factory_new(int x, int y):
    cdef MyClass cpp_obj = new MyClass(x, y)
    cdef PyMyClass py_obj = PyMyClass()
    py_obj.c_class = &cpp_obj
    return py_obj

Also, the cdef public my_func(MyClass var) is not needed. public is for making Cython functions available to C/C++, it does not mean the same thing it does in C++. If you do not intend to use my_func outside Python/Cython, public is not needed.

If you intend to use that function via Python then it must be def or cpdef, not cdef.

Finally, you probably want to define the external definition as nogil and use with nogil when calling C++ functions, for obvious reasons. For example,

cdef extern from "myclass.h" namespace "classes" nogil:
    <..>

cdef class PyMyClass:
    <..>

    def sum(self):
        cdef int ret
        with nogil:
            ret = self.c_class.sum()
        return ret


标签: c++ cython