在pybind11中引用c++分配的对象

Referencing a C++ allocated object in pybind11

本文关键字:分配 对象 c++ 引用 pybind11      更新时间:2023-10-16

我试图用pybind11创建一个python绑定,该绑定引用了一个c++实例,其内存在c++端处理。下面是一些示例代码:

import <pybind11/pybind11>
struct Dog {
    void bark() { printf("Bark!n"); }
};
int main()
{
  auto dog = new Dog;
  Py_Initialize();
  initexample(); // Initialize the example python module for import 
  // TBD - Add binding  between dog and example.dog .
  PyRun_StringFlags("import examplen"
                    "n"
                    "example.dog.bark()n"  // Access the C++ allocated object dog.
                    , Py_file_input, main_dict, main_dict, NULL);
  Py_Finalize();
}

我被困在如何创建python example.dog和c++ dog变量之间的链接。

我不能使用py:class_<Dog>.def(py::init<>()),因为这将分配一个新的Dog实例,这不是我想要的。

我找到了自己问题的答案。诀窍是结合了以下两个概念:

  • 创建一个返回单例的独立函数
  • 创建到单例类的绑定,而不需要绑定构造函数。

下面的示例说明了该技术:

#include <Python.h>
#include <pybind11/pybind11.h>
namespace py = pybind11;
using namespace pybind11::literals;
// Singleton to wrap
struct Singleton
{
  Singleton() : x(0) {}
  int exchange(int n)  // set x and return the old value
  {
    std::swap(n, x);
    return n;
  }
  // Singleton reference 
  static Singleton& instance()
  {
    static Singleton just_one;
    return just_one;
  }
  int x;
};
PYBIND11_PLUGIN(example) {
    py::module m("example", "pybind11 example plugin");
    // Use this function to get access to the singleton
    m.def("get_instance",
          &Singleton::instance,
          py::return_value_policy::reference,
          "Get reference to the singleton");
    // Declare the singleton methods
    py::class_<Singleton>(m, "Singleton")
      // No init!
      .def("exchange",
           &Singleton::exchange,
           "n"_a,
           "Exchange and return the current value"
           )
      ;
    return m.ptr();
}
int main(int argc, char **argv)
{
  Py_Initialize();
  PyObject* main_module = PyImport_AddModule("__main__");
  PyObject* main_dict = PyModule_GetDict(main_module);
  initexample();
  // Call singleton from c++
  Singleton::instance().exchange(999);
  // Populate the example class with two static pointers to our instance.
  if (PyRun_StringFlags("import examplen"
                        "n"
                        "example.s1 = example.get_instance()n"
                        "example.s2 = example.get_instance()n",
                        Py_file_input, main_dict, main_dict, NULL) == nullptr)
      PyErr_Print();
  // Test referencing the singleton references
  if (PyRun_StringFlags("from example import *n"
                        "n"
                        "for i in range(3):n"
                        "  print s1.exchange(i*2+1)n"
                        "  print s2.exchange(i*2+2)n"
                        "print dir(s1)n"
                        "print help(s1.exchange)n"
                        ,
                        Py_file_input, main_dict, main_dict, NULL) == nullptr)
      PyErr_Print();
  Py_Finalize();
  exit(0);
}

自Pybind11 v2.2.0以来,另一种方法是可能的,使用自定义构造函数包装:python init方法不再需要调用c++构造函数。你可以让它直接返回c++的单例实例。

声明在你的例子中可能看起来像:

   // Declare the singleton methods
   py::class_<Singleton>(m, "Singleton")
      .def("init", [](){
          return std::unique_ptr<Singleton, py::nodelete>(&Singleton::instance());
      });
在python中

:

myInstance1 = Singleton()
myInstance2 = Singleton()

myInstance1和myInstance2指向同一个c++对象。

是的,我知道这个答案很晚了,但是之前提供的解决方案要么过时了,要么用一种模糊的方式解决问题。

其他答案的最大问题是它们同时使用pybind和原始Python接口。使用pybind,你可以使用一个更简单、更好的接口来连接解释器。

遵循可以解决问题的实现

你会注意到的第一件事是我们使用了"embed.h"头文件。这为我们提供了创建嵌入式模块的函数。

再往下,我们使用PYBIND11_EMBEDDED_MODULE代替常规的PYBIND11_MODULE或过时的PYBIND11_PLUGIN。这是一个专门用于嵌入的宏。

下一个有趣的部分是我们为结构定义的类型。除了Dog类型,我们还使用shared_ptr<Dog>类型。这对于处理实例是至关重要的。当main模块超出作用域并开始清理时,它需要知道类/结构是shared_ptr类型的,否则你会得到一个seg错误(原始指针在这里不可用,我个人认为这是一件好事)。

最后要指出的是,我们实际上使用pybind11::scoped_interpreter类作为解释器,而不使用Raw Python接口。

#include"pybind11pybind11.h"
#include"pybind11embed.h"
#include<iostream>
namespace py = pybind11;
struct Dog {
    void bark() { std::cout << "Bark!n";  }
};
PYBIND11_EMBEDDED_MODULE(DogModule, m) {
    // look at the types! we have a shared_ptr<Dog>!
    py::class_<Dog, std::shared_ptr<Dog>>(m, "DogModule")
        .def("bark", &Dog::bark);
}

int main(int argc, char **argv) 
{
    // Create Python Interpreter
    py::scoped_interpreter guard;
    // Create Dog Instance
    std::shared_ptr<Dog> ptr = std::make_shared<Dog>();
    // Import the DogModule & Assign the instance to a name in python
    py::module main = py::module::import("__main__");
    main.import("DogModule");
    main.attr("dogInstance") = ptr;
    // Call the bark method from python
    py::exec("dogInstance.bark()");

    getchar();
    return 0;
}