首页 > 解决方案 > 在 boost-python 中将 Python 对象的所有权转移到 C++

问题描述

我正在尝试通过 boost-python 向 Python 公开一个 C++ gui 应用程序,并且遇到了一些僵局。我正在使用 boost-python 1.67 和 Python 3.6。目标平台是Windows x64,编译器是VS2017。

在 C++ 中,我有一个接口,让我们调用它Foo,还有另一个接口,让我们调用它FooFactory。我还有一个 FooFactories 注册表,其中包含所有已知的 FooFactories。我查询此注册表以找到一个工厂,然后使用该工厂创建 Foos。

Foo并且FooFactory是抽象的 C++ 类。在 C++ 应用程序中,我已经有许多 和 的实现,FooFooFactory想让应用程序的用户能够编写 Python 的实现,Foo然后FooFactory可以像任何其他的FooFooFactory.

下面是 Foo 和 FooFactory 的简化定义:

#include <string>
class Foo
{
public:
  virtual ~Foo() {}
  virtual std::string getName() = 0;
  virtual void doFooThing() = 0;
};

class FooFactory
{
public:
  virtual ~FooFactory() {}
  virtual Foo* buildFoo() = 0;
};

这是我用来将上述类公开给 boost-python 的包装器的定义:

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

class FooWrapper : public Foo, public boost::python::wrapper<Foo>
{
public:
  ~FooWrapper() {}
  std::string getName() override
  {
    try
    {
      if (boost::python::override func = get_override("getName"))
        return func();

        PyErr_SetString(PyExc_NotImplementedError, "'getName' unimplemented");
        boost::python::throw_error_already_set();
    }
    catch ([[maybe_unused]] boost::python::error_already_set& error)
    {
        PyErr_PrintEx(0);
    }

    return std::string();
  }

  void doFooThing() override
  {
    try
    {
      if(boost::python::override func = get_override("doFooThing"))
      {
        func();
        return;
      }

      PyErr_SetString(PyExc_NotImplementedError, "'doFooThing'");
      boost::python::throw_error_already_set();
    }
    catch([[maybe_unused]] boost::python::error_already_set& error)
    {
      PyErr_PrintEx(0);
    }
  }
};

class FooFactoryWrapper : public FooFactory, public boost::python::wrapper<FooFactory>
{
public:
  ~FooFactoryWrapper() {}

  Foo* build() override
  {
    try
    {
      if(boost::python::override func = get_override("build"))
        return func();

      PyErr_SetString(PyExc_NotImplementedError, "'build' not implemented");
      boost::python::throw_error_already_set();
    }
    catch ([[maybe_unused]] boost::python::error_already_set& error)
    {
        PyErr_PrintEx(0);
    }

    return nullptr;
  }
};

void registerFooFactory(FooFactory* factory)
{
  // fooFactoryRegistry omitted in the interest of brevity
  fooFactoryRegistry->add(factory)
}

这些对象被导出到 python,如下所示:

namespace py = boost::python;
py::class_<FooWrapper, boost::noncopyable>("Foo")
  .def("getName", py::pure_virtual(&Foo::getName));

py::class_<FooFactoryWrapper, boost::noncopyable>("FooFactory")
  .def("build", py::pure_virtual(&FooFactory::build), py::return_value_policy<py::manage_new_object>());

py::def("registerFooFactory", &registerFooFactory);

这是 Python 中添加 Foo 和 FooFactory 的示例脚本:

import my_module as m

class FooDerived(m.Foo):
  def getName(self):
    return "FooDerived"

class FooFactoryDerived(m.FooFactory):
  def build(self):
    return FooDerived()

m.registerFactory(FooFactoryDerived())

Foo在 Python 脚本终止后,注册表然后在应用程序的其他地方用于实例化s。任何用 Python 编写FooFooFactory实现必须在进程生命周期内保持可用。

当我运行我在 C++ 应用程序中给出的示例时,boost-python 会引发一个

ReferenceException: Attempt to return dangling pointer to object of type: class Foo

在 Python行return FooDerived()。我已经尝试了 FooFactoryWrapper 和 FooFactory 的多种变体,它们具有各种不同的, std::unique_ptrstd::shared_ptr但没有一个能够可靠地工作(要么我在编译时得到模糊的模板错误,要么在运行时得到相同的结果 - a )。std::auto_ptrboost::shard_ptrReferenceException

我能够通过一个令人反胃的可怕黑客来解决这个问题,我Py_INCREF()PyObject*内部手动调用 func() 的返回值,如下所示:

if(boost::python::override func = get_override("build"))
{
  boost::python::detail::method_result result = func();
  Py_INCREF(result.m_obj.get())
  return result;
}

但是这个解决方案对于生产代码是不可接受的。即使我这样做了,在 C++ 中的任何时候,当我在 Foo 的 Python 实现上调用 delete 时,Python 实现也会崩溃。作为一个额外的限制,我暂时没有调试符号。

我相信我从根本上误解了 boost-python 的内存所有权语义。因此,我有几个问题:

  1. 这种类型的接口可以在 boost-python 中公开吗?
  2. 是否可以在所有权转移给 C++ 的 Python 中创建 C++ 接口的 Python 实现实例?

我查看了许多其他示例,包括Boost::python: object 在覆盖的方法中破坏自身Boost.Python: How to expose std::unique_ptr而我无法让它适用于我的用例(有两个抽象类),虽然我不确定这是因为我犯了错误或误解了问题,还是因为不可能。

有任何想法吗?

标签: pythonc++boostunique-ptrboost-python

解决方案


推荐阅读