Samuel SILVESTER
Samuel SILVESTER

Reputation: 87

Passing a C++ instance into Python outside of BOOST_PYTHON_MODULE macro

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

Answers (2)

Tanner Sansbury
Tanner Sansbury

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:

  • Expose TestObj via class_ after the interpreter has been initialized within main().
  • Import the statically linked 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

Samuel SILVESTER
Samuel SILVESTER

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

Related Questions