我正试图通过boost-python将C ++ gui应用程序公开给Python,但陷入了僵局。我正在使用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
一样在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", ®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())
Python脚本终止后,然后将注册表用于应用程序中的其他地方以实例化Foo
。用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
,std::auto_ptr
和boost::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的内存所有权语义。因此,我有很多问题:
我查看了许多其他示例,包括Boost::python: object destroys itself inside a overriden method和Boost.Python: How to expose std::unique_ptr,尽管我已经使用了两个抽象类,但我无法使它适用于我的用例(使用两个抽象类)不确定那是因为我犯了错误或误解了该问题,还是因为这不可能。
有什么想法吗?