使用 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 作为 PyCapsule
的 shared_ptr<Foo>
成员,我在 Foo
class 的 pybind11 中捕获了它。我正在从胶囊中解压 shared_ptr<Foo>
,return 将其转换为 python 并且用户可以使用 Foo
的 pybind11 绑定在 python 中操作此对象.
然后我需要将它放回 pybind11 中的胶囊中,然后 return 放回 Bar
。
Bar 的 python API 在 PyObject
和 PyCapsule
上运行,因为 cython 允许这样做。 pybind11 因此 Foo 的 API 不接受这些类型,我被迫使用 pybind11::object
和 pybind11::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_foo
和 foo_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 文件,其中包含 PyBarStruct
和 PyBarType
的定义(您可能不需要后者)。我还定义了一个基本的 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
.
更清晰的方法
我有两个公开 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 作为 PyCapsule
的 shared_ptr<Foo>
成员,我在 Foo
class 的 pybind11 中捕获了它。我正在从胶囊中解压 shared_ptr<Foo>
,return 将其转换为 python 并且用户可以使用 Foo
的 pybind11 绑定在 python 中操作此对象.
然后我需要将它放回 pybind11 中的胶囊中,然后 return 放回 Bar
。
Bar 的 python API 在 PyObject
和 PyCapsule
上运行,因为 cython 允许这样做。 pybind11 因此 Foo 的 API 不接受这些类型,我被迫使用 pybind11::object
和 pybind11::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_foo
和 foo_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 文件,其中包含 PyBarStruct
和 PyBarType
的定义(您可能不需要后者)。我还定义了一个基本的 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
.