从 Python 将不透明数据传递给 C++ 回调

Posted

技术标签:

【中文标题】从 Python 将不透明数据传递给 C++ 回调【英文标题】:Passing opaque data to C++ callback from Python 【发布时间】:2014-12-16 21:27:54 【问题描述】:

我正在将 Python(使用 boost::python)嵌入到使用回调的应用程序插件中。本质上,我想做这样的事情:

在 Python 中(比如 test.py):

def do_something():
  ...

register_callback(do_something)

在 C++ 端,我注册了 register_callback() 函数:

void register_callback(boost::python::object& o)

    // Access some persistent state information


BOOST_PYTHON_MODULE(foo)

  boost::python::def("register_callback", register_callback);


void library_entry()

  PyImport_AppendInittab("foo", initfoo);

  PyInitialize();

  // Create some persistent state information

  boost::python::exec_file("test.py", ...);

这里的问题是我需要创建 Python 上下文并将内容存储在我返回给应用程序的不透明指针中。当 Python 回调到 C++ 中时,我需要恢复那个不透明的值。例如:

应用程序 -- (调用) --> 我的 C++ 库 -- (运行) --> Python 脚本 -- (调用) --> 我的 C++ 库

对于最后一步,我需要恢复一些不透明的数据。此外,我需要那些不透明的数据是持久的。对我的 C++ 库的调用是来自应用程序的回调。我遇到的问题是试图弄清楚如何将该状态信息传递给 C++ register_callback() 函数。我尝试过类似的方法:

namespace bp = boost::python;

class state_info_t

;

void register_callback(std::shared_ptr<state_info_t>& state, bp::object& o);

  // Access some persistent state information


// Create some persistent state information
std::shared_ptr<state_info_t> state = std::make_shared<state_info_t>();

PyImport_AppendInittab("foo", initfoo);

Py_Initialize();

std::shared_ptr<bp::object> main_module = std::make_shared<bp::object>(bp::handle<>(bp::borrowed(PyImport_AddModule("__main__"))));
bp::object main_namespace = main_module->attr("__dict__");
std::shared_ptr<bp::object> foo_module = std::make_shared<bp::object>(bp::handle<>(PyImport_ImportModule("foo")));

main_namespace["foo"] = *foo_module;

bp::scope foo_scope(*foo_module);

// Both of these fail with _a lot_ of errors, most related to "no matching function call to 'get_signature'
bp::def("register_callback",
        [&](bp::object& o)  register_callback(state, o); ,
        bp::arg("func"));

bp::def("register_callback",
        std::bind(register_callback, state, std::placeholders::_1),
        bp::arg("func"));

我的另一个想法是将持久数据存储在模块的字典中。但我不知道如何在回调中恢复它。例如:

// Create some persistent state information
std::shared_ptr<state_info_t> state = std::make_shared<state_info_t>();

PyImport_AppendInittab("foo", initfoo);

Py_Initialize();

std::shared_ptr<bp::object> main_module = std::make_shared<bp::object>(bp::handle<>(bp::borrowed(PyImport_AddModule("__main__"))));
bp::object main_namespace = main_module->attr("__dict__");
std::shared_ptr<bp::object> foo_module = std::make_shared<bp::object>(bp::handle<>(PyImport_ImportModule("foo")));
bp::object foo_namespace = main_module->attr("__dict__");

main_namespace["foo"] = *foo_module;
foo_namespace["state"] = bp::handle<>(state); // Whatever the appropriate wrapper is

然后在 register_callback 的 C++ 端:

void register_callback(bp::object& o)

  // How do I extract "state" from the context?

我不喜欢最后一个,因为它会将状态信息暴露给脚本。

我不想将状态信息设为全局,因为可能有多个正在运行的 Python 实例。无论如何,对于多个 Python 实例,我仍然需要一种方法来确定正在运行的 Python 实例以选择适当的状态信息。

【问题讨论】:

我正在使用的一个选项是使用 PyCapsule 并将其存储在模块字典中(这是我最后提到的那种)。鉴于我认为模块字典在所有解释器之间共享,我不确定它是否会起作用。虽然也许 sys.modules 字典... 【参考方案1】:

我想我找到了实现我想要的方法。

我可以创建一个类,而不是实际创建一个模块,然后将该类的一个实例放入 sys.modules 中(参见this answer)。

所以我做了这样的事情:

namespace bp = boost::python;

class foo

  public:
    void register_callback(bp::object&);
;

...

// Register foo with Python (in the current scope)
bp::object foo_class = bp::class_<foo>("foo")
  .def("register_callback", &foo::register_callback);

// Get sys.modules
bp::object obj(bp::handle<>(bp::borrowed(PyImport_GetModuleDict())));

// Add an instance of foo to sys.modules
obj["foo"] = foo_class();

现在,在 python 中:

import foo

def func():
  ...

foo.register_callback(func)

这似乎使类实例显示为模块,并作为模块工作。

【讨论】:

以上是关于从 Python 将不透明数据传递给 C++ 回调的主要内容,如果未能解决你的问题,请参考以下文章

将数据传递给 Windows 控制台控制处理程序

如何将表模型数据传递给 qml?

jQuery $.ajax(),将成功数据传递给单独的函数

通过 Django 将 Python 数据传递给 JavaScript

Grails render v respond - 将数据传递给控制器​​的约定

React:将传入的服务数据传递给孩子