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

时间:2019-07-19 15:37:06

标签: python c++ cython pybind11

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

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

Bar返回shared_ptr<Foo>成员作为PyCapsule,我在Foo类的pybind11中捕获了它。我正在从胶囊中取出shared_ptr<Foo>的包装,将其返回给python,用户可以使用Foo的pybind11绑定在python中对该对象进行操作。

然后我需要将其放回pybind11的胶囊中,并返回到Bar

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

在尝试使用pybind11中创建的pybind11::capsule之前,一切正常,直到类Bar的cython方法中,该方法期望使用PyCapsule*

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

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

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

酒吧的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)

1 个答案:

答案 0 :(得分:1)

您的代码中有太多未完成的细节,我什至无法测试您的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>的{​​{3}}。 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
    }

从C ++到Python的转换程序类似于

 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定义的函数并不安全。 / p>

此代码未经测试,因此几乎可以肯定会出错,但是应该提供比PyCapsule更干净的方法。