python - 在 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++ 应用程序中,我已经有许多 和 的实现,Foo
我FooFactory
想让应用程序的用户能够编写 Python 的实现,Foo
然后FooFactory
可以像任何其他的Foo
和FooFactory
.
下面是 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", ®isterFooFactory);
这是 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 编写Foo
的FooFactory
实现必须在进程生命周期内保持可用。
当我运行我在 C++ 应用程序中给出的示例时,boost-python 会引发一个
ReferenceException: Attempt to return dangling pointer to object of type: class Foo
在 Python行return FooDerived()
。我已经尝试了 FooFactoryWrapper 和 FooFactory 的多种变体,它们具有各种不同的, std::unique_ptr
,std::shared_ptr
但没有一个能够可靠地工作(要么我在编译时得到模糊的模板错误,要么在运行时得到相同的结果 - a )。std::auto_ptr
boost::shard_ptr
ReferenceException
我能够通过一个令人反胃的可怕黑客来解决这个问题,我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 的内存所有权语义。因此,我有几个问题:
- 这种类型的接口可以在 boost-python 中公开吗?
- 是否可以在所有权转移给 C++ 的 Python 中创建 C++ 接口的 Python 实现实例?
我查看了许多其他示例,包括Boost::python: object 在覆盖的方法中破坏自身和Boost.Python: How to expose std::unique_ptr而我无法让它适用于我的用例(有两个抽象类),虽然我不确定这是因为我犯了错误或误解了问题,还是因为不可能。
有任何想法吗?
解决方案
推荐阅读
- c# - 列的gridview总和
- javascript - 如何从位于非 www 文件夹但位于 www 同一级别的另一个文件夹中的 REACT 访问 XML 文件?
- c++ - 从“向量”没有合适的转换
“到”矢量 “*存在吗? - sql - 子表合并到主表的查询计划突然加入了急切的spool,性能损失巨大
- angular - 过期后的Angularfire2刷新令牌
- java - 将相邻元素相乘并比较哪个具有更高的值
- docker - 持续交付 - 微服务发布/版本控制
- sql-server - 更改服务器/实例而不将连接字符串更改为 SQL Server
- node.js - 节点应用程序的正确错误日志记录
- javascript - 如何从表中发布作为来自服务器的 GET 请求的响应的行的内容?