在boost-python

时间:2018-08-03 12:38:57

标签: python c++ boost unique-ptr boost-python

我正试图通过boost-python将C ++ gui应用程序公开给Python,但陷入了僵局。我正在使用boost-python 1.67和Python 3.6。目标平台是Windows x64,编译器是VS2017。

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

FooFooFactory是抽象的C ++类。在C ++应用程序中,我已经有许多FooFooFactory的实现,并且我希望使应用程序的用户能够在Python中编写FooFooFactory的实现。然后可以像其他FooFooFactory一样在C ++应用程序中使用。

这是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())

Python脚本终止后,然后将注册表用于应用程序中的其他地方以实例化Foo。用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_ptrstd::auto_ptrboost::shard_ptr的各种阴影,但是没有一个可靠地起作用(在编译时会出现晦涩的模板错误时间或运行时的相同结果-ReferenceException)。

我能够解决一个令人讨厌的骇客骇客,我在func()返回值内的Py_INCREF()上手动调用了PyObject*,就像这样:

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 destroys itself inside a overriden methodBoost.Python: How to expose std::unique_ptr,尽管我已经使用了两个抽象类,但我无法使它适用于我的用例(使用两个抽象类)不确定那是因为我犯了错误或误解了该问题,还是因为这不可能。

有什么想法吗?

0 个答案:

没有答案