I'm starting to use Cython to interface some C++ code with Python. Since I need to do some asynchronous data acquisition in C++, I was planning to use callbacks from my C++ code to Python. So far I've been able to successfully call Python functions from C++, however, I can't call Python class methods from C++. I've seen several questions asked on StackOverflow about it, but no one provides a simple answer to a seemingly simple thing to do. This is a working toy example.
myclass.h:
#ifndef MYCLASS_H
#define MYCLASS_H
#define PY_SSIZE_T_CLEAN
#include <Python.h>
typedef void (*callbackfun)(PyObject* obj);
class MyClass
{
public:
void start(callbackfun user_func, PyObject* obj);
};
#endif // MYCLASS_H
myclass.cpp:
#include "myclass.h"
void MyClass::start(callbackfun user_func, PyObject* obj)
{
user_func(obj);
}
PyMyClass.pyx:
# distutils: language = c++
# cython: language_level=3
cdef extern from "myclass.h":
ctypedef void (*callbackfun)(object obj)
cdef cppclass MyClass:
MyClass()
void start(callbackfun user_func, object obj)
cdef void callback(self):
print('Called back')
cdef class PyMyClass:
cdef MyClass *c_myClass_ptr
def __cinit__(self):
self.c_myClass_ptr = new MyClass()
def __dealloc__(self):
del self.c_myClass_ptr
def start(self):
self.c_myClass_ptr.start(callback, self)
main.py:
from PyMyClass import PyMyClass
def main():
myClass = PyMyClass()
myClass.start()
if __name__ == "__main__":
main()
Now, if I convert the callback
function in PyMyClass.pyx
to a method, as follows:
PyMyClass.pyx:
# distutils: language = c++
# cython: language_level=3
cdef extern from "myclass.h":
ctypedef void (*callbackfun)(object obj)
cdef cppclass MyClass:
MyClass()
void start(callbackfun user_func, object obj)
cdef class PyMyClass:
cdef MyClass *c_myClass_ptr
def __cinit__(self):
self.c_myClass_ptr = new MyClass()
def __dealloc__(self):
del self.c_myClass_ptr
cdef void callback(self):
print('Called back')
def start(self):
self.c_myClass_ptr.start(self.callback, self)
Now the compiler complains with:
Compiling PyMyClass.pyx because it changed.
[1/1] Cythonizing PyMyClass.pyx
Error compiling Cython file:
------------------------------------------------------------
...
cdef void callback(self):
print('Called back')
def start(self):
self.c_myClass_ptr.start(self.callback, self) ^
------------------------------------------------------------
PyMyClass.pyx:24:36: Cannot assign type 'void (PyMyClass)' to 'callbackfun'
Traceback (most recent call last):
File "setup.py", line 22, in <module>
setup(ext_modules = cythonize(ext_modules))
File "/Users/andreac/anaconda3/lib/python3.7/site-packages/Cython/Build/Dependencies.py", line 1097, in cythonize
cythonize_one(*args)
File "/Users/andreac/anaconda3/lib/python3.7/site-packages/Cython/Build/Dependencies.py", line 1220, in cythonize_one
raise CompileError(None, pyx_file)
Cython.Compiler.Errors.CompileError: PyMyClass.pyx
Is there a (simple) fix to this issue? (And yes, I understand that there's also a potential reference counting issue with using PyObject*
)