Cython:具有自定义参数类型的STD ::功能回调

Cython: std::function callbacks with custom parameter types

本文关键字:STD 回调 类型 功能 参数 自定义 Cython      更新时间:2023-10-16

请在回答之前阅读此帖子:将封闭式从Cython传递到C

在接受的答案中,它整齐地展示了如何使用Boost Python将Python函数转换为std::function

按照此示例,我能够将std::function作为参数的功能包裹起来,并使用Python函数将其称为输入。但是,这仅在std::function参数为 intdoublestring等的原始内容时起作用。

关于如何为自定义类型制作这项工作的任何指南将受到高度赞赏。

这不是一个完整的答案 - 它假设您可以从我以前的答案中填补问题所基于的空白。不幸的是,它比这种情况要复杂一些。


只是为了定义问题 - 假设您有一个自定义C 类的参数,例如:

class cpp_class {
  // some non-trivial contents
};

因此,您的C 接口看起来像这样:

void call_some_std_func(std::function<void(cpp_class&)> callback) {
    callback(5,std::string("hello"));
}

要做的第一件事是为您的C 类编写一份Cython包装器(原则上,您可以改为Boost Python包装器)。在这里,您需要选择有关C 对象的"所有权"。首选是制作副本:

cdef extern from "cpp_file.hpp":
  cppclass cpp_class:
    pass # details
cdef class CyWrapper:
  cdef cpp_class* ptr
  def __dealloc__(self):
    del self.ptr
  # other details following standard wrapper pattern
cdef public make_CyWrapper(cpp_class& x):
  obj = CyWrapper()
  obj.ptr = new cpp_class(x)
  return obj

我已经创建了一个具有破坏函数的包装程序类,该类别可以处理内存和可公开访问的构造函数,可以从外部代码调用。此版本是安全的,因为您的包装器拥有其拥有的对象,因此无法写入无效的内存。但是,由于它制作了副本,因此您无法更改原始C 对象。

第二个选项是将指针固定到您不拥有的对象。代码基本上是相同的,除了您删除__dealloc__并避免在make_CyWrapper中制作副本:

obj.ptr = &x // instead of new cpp_class(x)

这是不安全的 - 您需要确保您的C 对象要列出Cython包装器 - 但允许您修改对象。

您还可以想象其他一些选择:您可以使用Cython包装器将现有对象的所有权(这样的方案都必须通过指针而不是参考来传递,否则它可以使用移动构造函数);您可以将C 类解构为以基本类型表示的表示,并将其传递给Python。您可以使用共享的指针来分配所有权;或者,一旦持有C 实例,您就有更精细的方式将Cython包装商标记为"无效"。


您接下来要做的事情取决于您是使用boost python(因为它方便,可呼叫的python对象)还是制作自己的版本。(我在上一个答案中表现出了两种可能性)。

假设Boost Python,您需要做两件事 - 告诉它有关转换的信息,并确保它导入包装器所定义的模块(如果您不这样做,则会遇到令人兴奋的细分故障)

struct convert_to_PyWrapper {
 static PyObject* convert(const cpp_class& rhs) {
    // the const_cast here is a bit dodgy, but was needed to make it work
    return make_CyWrapper(const_cast<cpp_class&>(rhs));
}
};
inline void setup_boost_python() {
    PyInit_your_module_name(); // named inityour_module_name in Python 2
    boost::python::to_python_converter<
        cpp_class,
        convert_to_PyWrapper>();
}

您需要在尝试使用回调之前确保您的Python/Cython代码调用" setup_boost_python"(如果将其放在模块级别上,则在导入上完成,这是理想的)。

>

如果您正在遵循我的"手动"方案(避免对Boost Python的依赖性),则需要修改call_obj Cython函数,该函数将C 用于Cython Type转换。

cdef public void call_obj(obj, cpp_class& c):
    obj(make_CyWrapper(c))

您还需要在使用前确保将包装器Cython模块导入(否则您会得到分割故障)。我在" py_object_wrapper.hpp"中做到了这一点

void operator()(cpp_class& a) {
    PyInit_your_module_name();
    if (held) {
        call_obj(held,a);
    }
}