使用 python 胶囊在 cython 和 pybind11 之间传输 c++ 对象



我有两个公开python API的c ++库,但使用两个不同的框架(pybind11和cython)。我需要使用 python 胶囊在它们之间(双向)传输一个对象。由于cython和pybind11使用python胶囊的方式不同,是否有可能使其工作?

我有库 A 定义了一个类Foo并使用 pybind11 将其公开给 python。库 B 使用 cython 公开其 API。LibB 拥有一台shared_ptr<Foo>它是 LibB 的一个类的成员,比如 -Bar

Bar 将shared_ptr<Foo>成员作为我在Foo类的 pybind11 中捕获的PyCapsule返回。我正在从胶囊中解压缩shared_ptr<Foo>,将其返回给 python,用户可以在 python 中使用 pybind11 绑定对这个对象进行操作Foo

然后我需要把它放回 pybind11 中的胶囊中,然后返回Bar.

Bar的python API在PyObjectPyCapsule上运行,因为cython允许这样做。 pybind11 因此 Foo 的 API 不接受这些类型,我被迫使用pybind11::objectpybind11::capsule.

一切正常,直到我尝试使用在pybind11中创建的pybind11::capsule的那一刻,在类Bar的cython方法中,该方法需要PyCapsule*

pybind11::capsule内的shared_ptr<Foo>已损坏,我的应用程序崩溃。

有没有人试图让这两个库相互交谈?

libA -> class Foo

namespace foo{
class Foo {
public:
void foo() {...}
}
}

libB ->类酒吧

namespace bar {
class Bar {
public:
PyObject* get_foo() {
const char * capsule_name = "foo_in_capsule";
return PyCapsule_New(&m_foo, capsule_name, nullptr);
}
static Bar fooToBar(PyObject * capsule) {
void * foo_ptr = PyCapsule_GetPointer(capsule, "foo_in_capsule");
auto foo  = static_cast<std::shared_ptr<foo::Foo>*>(foo_ptr);
// here the shared_ptr is corrupted (garbage numbers returned for use_count() and get() )
std::cout << "checking the capsule: " << foo->use_count() << " " << foo->get() << std::endl
Bar b;
b.m_foo = *foo; //this is what I would like to get
return b;
}
std::shared_ptr<Foo> m_foo;
};
}

pybind11 for Foo

void regclass_foo_Foo(py::module m)
{
py::class_<foo::Foo, std::shared_ptr<foo::Foo>> foo(m, "Foo");
foo.def("foo", &foo::Foo::foo);
foo.def_static("from_capsule", [](py::object* capsule) {
auto* pycapsule_ptr = capsule->ptr();
auto* foo_ptr = reinterpret_cast<std::shared_ptr<foo::Foo>*>(PyCapsule_GetPointer(pycapsule_ptr, "foo_in_capsule"));
return *foo_ptr;
});
foo.def_static("to_capsule", [](std::shared_ptr<foo::Foo>& foo_from_python) {
auto pybind_capsule = py::capsule(&foo_from_python, "foo_in_capsule", nullptr);
return pybind_capsule;
});
}

酒吧赛通

cdef extern from "bar.hpp" namespace "bar":
cdef cppclass Bar:
object get_foo() except +
def foo_to_bar(capsule):
b = C.fooToBar(capsule)
return b

在 Python 中将它们放在一起

from bar import Bar, foo_to_bar
from foo import Foo
bar = Bar(... some arguments ...)
capsule1 = bar.get_foo()
foo_from_capsule = Foo.from_capsule(capsule1)
// this is the important part - need to operate on foo using its python api
print("checking if foo works", foo_from_capsule.foo())
// and use it to create another bar object with a (possibly) modified foo object
capsule2 = Foo.to_capsule(foo_from_capsule)
bar2 = foo_to_bar(capsule2)

你的代码中有太多未完成的细节,我什至无法测试你的PyCapsule版本。我的观点是,问题在于共享指针的生存期 - 您的胶囊指向一个共享指针,该共享指针的生存期与它所在的Bar相关联。但是,胶囊可能会比这更长的寿命。您可能应该创建一个新shared_ptr<Foo>*(使用new),指向胶囊中的该,并定义一个析构函数(用于胶囊)来删除它。


我认为应该更好的替代方法概述如下:

纯粹根据C++类型编写类,因此get_foofoo_to_bar只需获取/返回shared_ptr<Foo>

PyBar定义为适当的 Cython 类,而不是使用胶囊:

cdef public class PyBar [object PyBarStruct, type PyBarType]:
cdef shared_ptr[Bar] ptr
cdef public PyBar PyBar_from_shared_ptr(shared_ptr[Bar] b):
cdef PyBar x = PyBar()
x.ptr = b
return x

这将生成一个包含PyBarStructPyBarType定义的头文件(您可能不需要后者)。我还定义了一个基本的模块级函数来从共享指针创建PyBar(并使其公开,因此它也出现在标题中)。

然后使用 PyBind11 定义一个自定义类型转换器来往/从shared_ptr<Bar>load是这样的:

bool load(handle src, bool) {
auto bar_mod = py::import("bar");
auto bar_type = py::getattr(bar_mod,"Bar");
if (!py::isinstance(src,bar_type)) {
return false;
}
// now cast to my PyBarStruct
auto ptr = reinterpret_cast<PyBarStruct*>(src.ptr());
value  = ptr->ptr; // access the shared_ptr of the struct
}

而对 Python caster 的C++是这样的

static handle cast(std::shared_ptr<Bar> src, return_value_policy /* policy */, handle /* parent */) {
auto bar_mod = py::import("bar"); // See note...
return PyBar_from_shared_ptr(src);
}

我确保在两个函数中包含py::import("bar"),因为我认为在模块导入到某个地方之前使用 Cython 定义的函数是不安全的,并且将其导入到脚轮中确实可以确保这一点。

这段代码未经测试,所以几乎肯定有错误,但应该提供比PyCapsule更干净的方法。

最新更新