Reputation: 87
I want to pass an instance of TestObj from the C++ code into python. The code posted here produces the error in cout: "No to_python (by-value) converter found for C++ type: class TestObj". If I move the object creation and main_module.attr("obj") = obj;
into the BOOST_PYTHON_MODULE macro, the code runs fine.
Similar things happen when I try passing a *TestObj
with or without boost::ptr.
testembed.py:
import sfgame
print("old x: " + str(obj.x))
obj.x = 10
print("new x: " + str(obj.x))
testobj.h
class TestObj{
public:
TestObj();
int x;
int getX();
void setX(int xv);
};
testobj.cpp
#include "TestObj.h"
TestObj::TestObj(){
}
int TestObj::getX(){
return x;
}
void TestObj::setX(int xv){
x = xv;
}
main.cpp
#include <boost/python.hpp>
#include "TestObj.h"
using namespace boost::python;
BOOST_PYTHON_MODULE(sfgame){
class_<TestObj>("TestObj")
.add_property("x", &TestObj::getX, &TestObj::setX)
;
}
int main(){
Py_Initialize();
object main_module = import("__main__");
object main_namespace = main_module.attr("__dict__");
TestObj obj;
try{
obj.setX(5);
main_module.attr("obj") = obj;
exec_file("testembed.py", main_namespace);
}
catch (const boost::python::error_already_set &){
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
std::string error;
error = boost::python::extract<std::string>(pvalue);
std::cout << error << std::endl;
}
system("PAUSE");
return 0;
}
Upvotes: 1
Views: 2795
Reputation: 51971
Passing a C++ object to Python via Boost.Python has the same pre-conditions regardless of context in which it is called: a to-Python converter must be registered for the C++ object's type.
When creating an instance of boost::python::class_<T>
, to-Python and from-Python converters are automatically registered for type T
. The BOOST_PYTHON_MODULE
macro only declares a Python module initialization function that Python will invoke when the module gets imported. In this particular case, the problem can be resolved by performing either of the following before passing a TestObj
instance to Python:
TestObj
via class_
after the interpreter has been initialized within main()
.sfgame
module. This requires explicitly adding the module initialization function tot he Python initialization table via PyImport_AppendInittab()
. See this answer for details.Calling the module initialization functions directly is not recommended. When called directly, the module itself is not created, but the types will be registered with Boost.Python. Upon importing the module, the module will be created and initialized, causing the types to be registered once more. In debug builds of Boost.Python, this will fail an assertion, and in release builds it will print a warning.
Here is a complete example demonstrating passing C++ objects to Python when embedding. In the example, the spam
type if exposed within the statically linked example
module, and the egg
type is exposed within the __main__
scope.
#include <boost/python.hpp>
// Mockup models.
struct spam {};
struct egg {};
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::class_<spam>("Spam", python::init<>());
}
int main()
{
// Explicitly add initializers for staticly linked modules.
PyImport_AppendInittab("example", &initexample);
// Initialize Python.
Py_Initialize();
namespace python = boost::python;
try
{
// Create the __main__ module.
python::object main_module = python::import("__main__");
python::object main_namespace = main_module.attr("__dict__");
// Import the example module, this will cause the example module's
// initialization function to be invoked, registering the spam type.
// >>> import example
python::import("example");
// >>> spam = example.Spam()
spam spam;
main_namespace["spam"] = spam;
// Expose egg, defining it within the main module.
// >>> class Egg: pass
main_namespace["Egg"] = python::class_<egg>("Egg", python::init<>());
// >>> egg = Egg()
egg egg;
main_namespace["egg"] = egg;
// Verify variables and their types.
python::exec(
"import example\n"
"assert(isinstance(spam, example.Spam))\n"
"assert(isinstance(egg, Egg))\n",
main_namespace);
}
catch (const python::error_already_set&)
{
PyErr_Print();
return 1;
}
// Do not call Py_Finalize() with Boost.Python.
}
Upvotes: 7
Reputation: 87
From the documentation:
"This macro generates two functions in the scope where it is used: extern "C" void initname(), and void init_module_name(), whose body must follow the macro invocation."
You need to call initsfgame();
.
Upvotes: 0