简体   繁体   中英

Set a python variable to a C++ object pointer with boost-python

I want to set a Python variable from C++ so that the C++ program can create an object Game* game = new Game(); in order for the Python code to be able to reference this instance (and call functions, etc). How can I achieve this?

I feel like I have some core misunderstanding of the way Python or Boost-Python works.

The line main_module.attr("game") = game is in a try catch statement, and the error (using PyErr_Fetch) is "No to_python (by-value) converter found for C++ type: class Game".

Eg

class_<Game>("Game")
        .def("add", &Game::add)
;

object main_module = import("__main__");
Game* game = new Game();
main_module.attr("game") = game; //This does not work

From Python:

import testmodule

testmodule.game.foo(7)

When dealing with language bindings, one often has to be pedantic in the details. By default, when a C++ object transgresses the language boundary, Boost.Python will create a copy, as this is the safest course of action to prevent dangling references. If a copy should not be made, then one needs to be explicit as to the ownership of the C++ object:

  • To pass a reference to a C++ object to Python while maintaining ownership in C++, use boost::python::ptr() or boost::ref() . The C++ code should guarantee that the C++ object's lifetime is at least as long as the Python object. When using ptr() , if the pointer is null, then the resulting Python object will be None .
  • To transfer ownership of a C++ object to Python, one can apply the manage_new_object ResultConverterGenerator , allowing ownership to be transferred to Python. C++ code should not attempt to access the pointer once the Python object's lifetime ends.
  • For shared ownership, one would need to expose the class with a HeldType of a smart pointer supporting shared semantics, such as boost::shared_ptr .

Once the Python object has been created, it would need to be inserted into a Python namespace to be generally accessible:

  • From within the module definition, use boost::python::scope to obtain a handle to the current scope. For example, the following would insert x into the example module:

     BOOST_PYTHON_MODULE(example) { boost::python::scope().attr("x") = ...; // example.x } 
  • To insert into the __main__ module, one can import __main__ . For example, the following would insert x into the __main__ module:

     boost::python::import("__main__").attr("x") = ...; 

Here is an example demonstrating how to directly construct the Python object from C++, transfer ownership of a C++ object to Python, and construct a Python object that references a C++ object:

#include <iostream>
#include <boost/python.hpp>

// Mockup model.
struct spam
{
  spam(int id)
    : id_(id)
  {
    std::cout << "spam(" << id_ << "): "  << this << std::endl;
  }

  ~spam()
  {
    std::cout << "~spam(" << id_ << "): " << this << std::endl;
  }

  // Explicitly disable copying.
  spam(const spam&) = delete;
  spam& operator=(const spam&) = delete;

  int id_;
};

/// @brief Transfer ownership to a Python object.  If the transfer fails,
///        then object will be destroyed and an exception is thrown.
template <typename T>
boost::python::object transfer_to_python(T* t)
{
  // Transfer ownership to a smart pointer, allowing for proper cleanup
  // incase Boost.Python throws.
  std::unique_ptr<T> ptr(t);

  // Use the manage_new_object generator to transfer ownership to Python.
  namespace python = boost::python;
  typename python::manage_new_object::apply<T*>::type converter;

  // Transfer ownership to the Python handler and release ownership
  // from C++.
  python::handle<> handle(converter(*ptr));
  ptr.release();

  return python::object(handle);
}

namespace {
spam* global_spam;
} // namespace

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  // Expose spam.
  auto py_spam_type = python::class_<spam, boost::noncopyable>(
      "Spam", python::init<int>())
    .def_readonly("id", &spam::id_)
    ;

  // Directly create an instance of Python Spam and insert it into this
  // module's namespace.
  python::scope().attr("spam1") = py_spam_type(1);

  // Construct of an instance of Python Spam from C++ spam, transfering
  // ownership to Python.  The Python Spam instance will be inserted into
  // this module's namespace.
  python::scope().attr("spam2") = transfer_to_python(new spam(2));

  // Construct an instance of Python Spam from C++, but retain ownership of
  // spam in C++.  The Python Spam instance will be inserted into the
  // __main__ scope.
  global_spam = new spam(3);
  python::import("__main__").attr("spam3") = python::ptr(global_spam);
}

Interactive usage:

>>> import example
spam(1): 0x1884d40
spam(2): 0x1831750
spam(3): 0x183bd00
>>> assert(1 == example.spam1.id)
>>> assert(2 == example.spam2.id)
>>> assert(3 == spam3.id)
~spam(1): 0x1884d40
~spam(2): 0x1831750

In the example usage, note how Python did not destroy spam(3) upon exit, as it was not granted ownership of the underlying object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM