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)
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