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

Transfer a c++ object between cython and pybind11 using python capsules

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

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

Bar returns 作为 PyCapsuleshared_ptr<Foo> 成员,我在 Foo class 的 pybind11 中捕获了它。我正在从胶囊中解压 shared_ptr<Foo>,return 将其转换为 python 并且用户可以使用 Foo 的 pybind11 绑定在 python 中操作此对象.

然后我需要将它放回 pybind11 中的胶囊中,然后 return 放回 Bar

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

一切正常,直到我尝试使用在 pybind11 中创建的 pybind11::capsule 的那一刻,在 class Bar 的 cython 方法中,它期望 PyCapsule*.

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

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

libA -> class Foo

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

libB -> class 栏

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;
    };
}

Foo 的 pybind11

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;
    });
}

酒吧的 cython

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++ 类型编写 classes,因此 get_foofoo_to_bar 只是 take/return shared_ptr<Foo>.

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

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

这会生成一个 header 文件,其中包含 PyBarStructPyBarType 的定义(您可能不需要后者)。我还定义了一个基本的 module-level 函数来从一个共享指针创建一个 PyBar(并使它也成为 public,所以它也出现在 header 中)。

然后用PyBind11定义a custom type-casterto/fromshared_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-defined 函数是不安全的,并且在脚轮中导入它确保。

此代码未经测试,因此几乎肯定有错误,但应该提供比 PyCapsule.

更清晰的方法