Hi,
I have a unique issue with Cython where I need to wrap a number of classes that are inherited from each other, which using the Cython extension types is perfectly fine. I then need to be able to extend the most derived extension type in Python for users to use.
- The base class (Base) has a C++ pure virtual function (call_me())
- The middle class (Derived) has overridden the base classes pure virtual function (call_me()) and also another pure virtual function (op1()).
- The Python class then extends Derived, and overrides op1(), we then want to pass an instance of this Python class, to another Cython extension type that can invoke the call_me() function.
The issue as I see it, is that the instantiation of the Python class (SampleImpl), never properly instantiates the extension types correctly. There doesn't seem to be a way to construct the wrapped C++ pointer types inside these extension types.
For a time we were using SWIG for wrapping our C++ code, and SWIG provided such a feature called "Directors" to allow a wrapped C++ class to be extended in Python. Unfortunately SWIG didn't work out for us, for other reasons, and Cython feels like the right fit for us, but this issue is a show-stopper for us.
Does such a feature exist in Cython? If not, then would it be a major undertaking to implement such a feature.
Environment:
- Windows 10
- Python 3.10 (x64)
- Cython 0.29.24
Output:
> py setup.py build_ext -i
> py test_app.py
[Tester::call_call_me()]: error Base pointer is NULL
Any help would be graciously appreciated.
Thanks,
Keith
====
// my_app.hpp
#ifndef _MY_APP_HPP__
#define _MY_APP_HPP__
#include <iostream>
class Base {
public:
virtual void call_me() = 0;
};
class Derived {
public:
virtual void op1() = 0;
virtual void call_me() {
std::cout << "In Derived::call_me()" << std::endl;
}
};
class Tester {
public:
void call_call_me(Base* base) {
if (!base) {
std::cerr << "[Tester::call_call_me()]: error Base pointer is NULL" << std::endl;
}
else {
base->call_me();
}
}
};
#endif // _MY_APP_HPP__
-----------
// my_app.cpp
#include "my_app.hpp"
-----------
# sample_mod.pxd
cdef extern from "my_app.hpp":
cdef cppclass Base:
pass
cdef cppclass Derived(Base):
void op1()
void call_me()
cdef cppclass Tester:
void call_call_me(Base* base)
-----------
# sample_mod.pyx
cimport sample_mod
cdef class PyBase:
cdef sample_mod.Base* _base
def __cinit__(self):
self._base = new sample_mod.Base()
cdef sample_mod.Base* get_ptr(self):
return self._base
cdef class PyDerived(PyBase):
cdef sample_mod.Derived* _derived
def __cinit__(self):
self._derived = new sample_mod.Derived()
@staticmethod
cdef create_py_derived(sample_mod.Derived* der):
ret = PyDerived()
ret._derived = der
return ret
def call_me(self):
self._derived.call_me()
cdef class PyTester:
cdef sample_mod.Tester* _tester
def __cinit__(self):
self._tester = new sample_mod.Tester()
def call_call_me(self, base_ptr : PyBase):
self._tester.call_call_me(base_ptr.get_ptr())
-----------
#!/usr/bin/env python3
# setup.py
from setuptools import setup, Extension
from Cython.Build import cythonize
test_module = Extension(
'sample_mod',
sources=[
'sample_mod.pyx',
'my_app.cpp'
],
language = 'c++'
)
setup (name = 'My Module',
version = '0.1',
author = "",
description = "",
ext_modules = cythonize(
[test_module],
compiler_directives = {'language_level' : '3'},
)
)
-----------
#!/usr/bin/python3
# test_app.py
import sample_mod
class SampleImpl(sample_mod.PyDerived):
def op1(self):
print("*** in op1")
tester = sample_mod.PyTester()
sampl = SampleImpl()
tester.call_call_me(sampl)