How can I implement a C++ class in Python, to be called by C++?
There's two parts to this answer. First you need to expose your interface in Python in a way which allows Python implementations to override parts of it at will. Then you need to show your C++ program (in main
how to call Python.
Exposing the existing interface to Python:
The first part is pretty easy to do with SWIG. I modified your example scenario slightly to fix a few issues and added an extra function for testing:
// myif.hclass myif { public: virtual float myfunc(float a) = 0;};inline void runCode(myif *inst) { std::cout << inst->myfunc(5) << std::endl;}
For now I'll look at the problem without embedding Python in your application, i.e. you start excetion in Python, not in int main()
in C++. It's fairly straightforward to add that later though.
First up is getting cross-language polymorphism working:
%module(directors="1") module// We need to include myif.h in the SWIG generated C++ file%{#include <iostream>#include "myif.h"%}// Enable cross-language polymorphism in the SWIG wrapper. // It's pretty slow so not enable by default%feature("director") myif;// Tell swig to wrap everything in myif.h%include "myif.h"
To do that we've enabled SWIG's director feature globally and specifically for our interface. The rest of it is pretty standard SWIG though.
I wrote a test Python implementation:
import moduleclass MyCl(module.myif): def __init__(self): module.myif.__init__(self) def myfunc(self,a): return a*2.0cl = MyCl()print cl.myfunc(100.0)module.runCode(cl)
With that I was then able to compile and run this:
swig -python -c++ -Wall myif.i g++ -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7python mycl.py 200.010
Exactly what you'd hope to see from that test.
Embedding the Python in the application:
Next up we need to implement a real version of your mymain.cc. I've put together a sketch of what it might look like:
#include <iostream>#include "myif.h"#include <Python.h>int main(){ Py_Initialize(); const double input = 5.0; PyObject *main = PyImport_AddModule("__main__"); PyObject *dict = PyModule_GetDict(main); PySys_SetPath("."); PyObject *module = PyImport_Import(PyString_FromString("mycl")); PyModule_AddObject(main, "mycl", module); PyObject *instance = PyRun_String("mycl.MyCl()", Py_eval_input, dict, dict); PyObject *result = PyObject_CallMethod(instance, "myfunc", (char *)"(O)" ,PyFloat_FromDouble(input)); PyObject *error = PyErr_Occurred(); if (error) { std::cerr << "Error occured in PyRun_String" << std::endl; PyErr_Print(); } double ret = PyFloat_AsDouble(result); std::cout << ret << std::endl; Py_Finalize(); return 0;}
It's basically just standard embedding Python in another application. It works and gives exactly what you'd hope to see also:
g++ -Wall -Wextra -I/usr/include/python2.7 main.cc -o main -lpython2.7./main200.01010
The final piece of the puzzle is being able to convert the PyObject*
that you get from creating the instance in Python into a myif *
. SWIG again makes this reasonably straightforward.
First we need to ask SWIG to expose its runtime in a headerfile for us. We do this with an extra call to SWIG:
swig -Wall -c++ -python -external-runtime runtime.h
Next we need to re-compile our SWIG module, explicitly giving the table of types SWIG knows about a name so we can look it up from within our main.cc. We recompile the .so using:
g++ -DSWIG_TYPE_TABLE=myif -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7
Then we add a helper function for converting the PyObject*
to myif*
in our main.cc:
#include "runtime.h"// runtime.h was generated by SWIG for us with the second call we mademyif *python2interface(PyObject *obj) { void *argp1 = 0; swig_type_info * pTypeInfo = SWIG_TypeQuery("myif *"); const int res = SWIG_ConvertPtr(obj, &argp1,pTypeInfo, 0); if (!SWIG_IsOK(res)) { abort(); } return reinterpret_cast<myif*>(argp1);}
Now this is in place we can use it from within main()
:
int main(){ Py_Initialize(); const double input = 5.5; PySys_SetPath("."); PyObject *module = PyImport_ImportModule("mycl"); PyObject *cls = PyObject_GetAttrString(module, "MyCl"); PyObject *instance = PyObject_CallFunctionObjArgs(cls, NULL); myif *inst = python2interface(instance); std::cout << inst->myfunc(input) << std::endl; Py_XDECREF(instance); Py_XDECREF(cls); Py_Finalize(); return 0;}
Finally we have to compile main.cc with -DSWIG_TYPE_TABLE=myif
and this gives:
./main11
Minimal example; note that it is complicated by the fact that Base
is not pure virtual. There we go:
baz.cpp:
#include<string>#include<boost/python.hpp>using std::string;namespace py=boost::python;struct Base{ virtual string foo() const { return "Base.foo"; } // fooBase is non-virtual, calling it from anywhere (c++ or python) // will go through c++ dispatch string fooBase() const { return foo(); }};struct BaseWrapper: Base, py::wrapper<Base>{ string foo() const{ // if Base were abstract (non-instantiable in python), then // there would be only this->get_override("foo")() here // // if called on a class which overrides foo in python if(this->get_override("foo")) return this->get_override("foo")(); // no override in python; happens if Base(Wrapper) is instantiated directly else return Base::foo(); }};BOOST_PYTHON_MODULE(baz){ py::class_<BaseWrapper,boost::noncopyable>("Base") .def("foo",&Base::foo) .def("fooBase",&Base::fooBase) ;}
bar.py
import syssys.path.append('.')import bazclass PyDerived(baz.Base): def foo(self): return 'PyDerived.foo'base=baz.Base()der=PyDerived()print base.foo(), base.fooBase()print der.foo(), der.fooBase()
Makefile
default: g++ -shared -fPIC -o baz.so baz.cpp -lboost_python `pkg-config python --cflags`
And the result is:
Base.foo Base.fooPyDerived.foo PyDerived.foo
where you can see how fooBase()
(the non-virtual c++ function) calls virtual foo()
, which resolves to the override regardless whether in c++ or python. You could derive a class from Base in c++ and it would work just the same.
EDIT (extracting c++ object):
PyObject* obj; // givenpy::object pyObj(obj); // wrap as boost::python object (cheap)py::extract<Base> ex(pyObj); if(ex.check()){ // types are compatible Base& b=ex(); // get the wrapped object // ...} else { // error}// shorter, thrwos when conversion not possibleBase &b=py::extract<Base>(py::object(obj))();
Construct py::object
from PyObject*
and use py::extract
to query whether the python object matches what you are trying to extract: PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();
Quoting http://wiki.python.org/moin/boost.python/Inheritance
"Boost.Python also allows us to represent C++ inheritance relationships so that wrapped derived classes may be passed where values, pointers, or references to a base class are expected as arguments."
There are examples of virtual functions so that solves the first part (the one with class MyCl(myif))
For specific examples doing this, http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions
For the line myif c = MyCl(); you need to expose your python (module) to C++. There are examples here http://wiki.python.org/moin/boost.python/EmbeddingPython