3

I'm trying to extend functionality of my lib written in C++ with Python and Cython. I have class MyClass in C++ which 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)

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Zhu
  • 57
  • 8
  • Not clear what you are trying to do. _Is it possible to just put it in?_ Put what in? Where? Are you trying to call PyMyClass from Python? How would it be called? Are you instead trying to call Cython built PyMyClass from C++? – danny Nov 02 '17 at 11:59
  • @danny, i've edited question. Hope now it more specific – Zhu Nov 02 '17 at 12:24
  • This might be helpful: https://stackoverflow.com/questions/33677231/how-to-expose-a-function-returning-a-c-object-to-python-without-copying-the-ob/33679481#33679481 – DavidW Nov 02 '17 at 12:26

1 Answers1

3

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
danny
  • 5,140
  • 1
  • 19
  • 31
  • Great! That is exactly that i needed! – Zhu Nov 03 '17 at 08:11
  • Yes, defined my_func as public because i want to use it from C++ to have access to Python functions. And do you know why i get an error "Cannot convert 'MyClass *' to Python object" if i define PyMyClass_factory in different .pyx file? Even if i copy all c++ MyClass declarations from pymyclass.pyx – Zhu Nov 03 '17 at 08:17
  • 1
    You should put the definition in a pxd file and cimport it. The error you're getting is because it doesn't know the definition of PyMyClass_factory and so assumes that it's a standard Python function. – DavidW Nov 03 '17 at 11:29