如何在本机回调中使用Cython cdef类成员方法

时间:2017-09-13 00:56:25

标签: c++ cython

我有一个大量回调驱动的C ++库。回调注册为std::function个实例。

回调注册功能可能类似于:

void register(std::function<void(int)> callback);

我可以通过制作cdef个对象来注册普通的libcpp.functional.function函数。说我有这个回调:

cdef void my_callback(int n):
    # do something interesting with n

我可以成功注册:

cdef function[void(int)]* f_my_callback = new function[void(int)](my_callback)
register(deref(f_my_callback))

问题是我想注册cdef class 方法 作为回调。我们说我有一个班级:

cdef class MyClass:

    cdef function[void(int)]* f_callback
    py_callback = lambda x: None

    # some more init/implementation

    def __init__(self):
        # ** this is where the problem is **
        self.f_callback = new function[void(int)](self.member_callback)
        register(deref(self.f_callback))

    cdef void member_callback(self, int n) with gil:
        self.py_callback(n)

    def register_py(self, py_callback):
        self.py_callback = py_callback

self.f_callback = new function[void(int)](self.member_callback)行无法正常工作,因为function[T]构造函数正在查看MyClass参数(self)。在常规python中,执行self.member_callback基本上等同于将self部分应用于MyClass.member_callback,所以我认为这样会好,但事实并非如此。

我做了一个typedef:

ctypedef void (*member_type)(int)

然后,如果我演员,我可以让它编译:

self.f_callback = new function[void(int)](<member_type>self.member_callback)

但是当回调实际进入时,一切都被打破了。对self执行任何操作都会导致段错误。

&#39;权利&#39;将cython类成员方法作为C / C ++回调传递的方法?

修改

为了澄清,这个问题具体是关于将成员方法(即带self作为第一个参数的函数)传递给C ++函数,期望类型为std::function的参数。

如果我使用member_callback装饰@staticmethod并删除对self的所有引用,则一切都按预期工作。不幸的是,该方法需要访问self才能正确执行类实例的逻辑。

2 个答案:

答案 0 :(得分:1)

std::function可以采用一系列参数。在它最简单的形式中,它需要一个直接映射到其模板类型的函数指针,这就是Cython包装器真正设置为应对的全部内容。对于包装C ++成员函数,您通常使用std::mem_funstd::mem_fun_ref来获取相关类实例的副本或引用(在现代C ++中,您也可以选择lambda)功能,但这真的只是给你相同的选项)。

将其转换为Python / Cython,您需要将PyObject*保存到您正在调用其成员的对象,并由此自行负责处理引用计数。不幸的是,Cython目前无法为您生成包装器,因此您需要自己编写一个包装器。基本上你需要一个可调用的对象来处理引用计数。

a previous answer中,我展示了两种创建这种包装器的方法(其中一种是手动的,第二种是使用Boost Python节省了工作量,它已经实现了非常相似的功能)。我在那里展示的方案将适用于匹配相关签名的任何Python可调用。虽然我使用标准的Python模块级函数来说明它,但它对于成员函数同样有效,因为instance.memberfunction生成一个绑定成员函数 - 一个Python可调用的函数也可以。

对于您的问题,唯一的区别是您使用cdef成员函数而不是def成员函数。这看起来应该可以工作但不会(最近版本的Cython尝试自动转换为Python可调用但它失败并出现运行时错误)。您可以创建一个lambda函数作为一个非常简单的包装器。从速度的角度来看,这略微不理想,但具有使用现有代码的优势。

您需要对之前的答案进行的修改如下:

  • 如果您尝试使用手册PyObjectWrapper版本,请更改参数类型以匹配您的签名(即将int, string&更改为int)。你不必为升级版本做这件事。

  • void register(std::function<void(int)> callback);的包装需要:

    cdef extern from "whatever.hpp":
      void register(PyObjWrapper)
      # or
      void register(bpo)
    

    取决于您使用的版本。这对于Cython来说是关于签名的,但由于这两个对象都是可调用的C ++对象,因此它们可以由C ++编译器自动转换为std::function

  • register称为register(PyObjWrapper(lambda x: self.member_callback(x)))register(get_as_bpo(lambda x: self.member_callback(x)))

可以创建一个专门针对使用cdef函数的更高效版本。它将基于一个与PyObjWrapper非常相似的对象。但是,如果我已编写的代码能够正常工作,我不愿意在没有明确理由的情况下这样做。

答案 1 :(得分:0)

我相信@DavidW的答案对于问题的简化版本是正确的。

为了清晰起见,我提出了我的问题的简化版本,但据我所知,我的问题的真实版本需要稍微不同的方法。

具体来说,在我的简化版本中,我表示将使用int调用回调。如果是这种情况,我可以想象提供一个python可调用(boost python或PyObjWrapper版本。)

但是,回调实际上是使用std::shared_ptr<T>调用的,其中T是库中的模板化类。 cdef回调需要对该实例执行某些操作,并最终调用python可调用。据我所知,没有办法用C ++对象调用python函数,如std::shared_ptr

@ DavidW的回复虽然很有帮助,但它让我找到了适用于这种情况的解决方案。我做了一个C ++实用程序函数:

// wrapper.hpp

#include <memory>
#include <functional>

#include "my_cpp_project.hpp"

template<typename T>
using cy_callback = void (*)(void*, std::shared_ptr<my_cpp_class<T>>);

template<typename T>
class function_wrapper {
public:

    static
    std::function<void(std::shared_ptr<my_cpp_class<T>>)>
    make_std_function(cy_callback<T> callback, void* c_self)
    {
        std::function<void(std::shared_ptr<my_cpp_class<T>>)>
        wrapper = [=](std::shared_ptr<my_cpp_class<T>> sample) -> void
        {
            callback(c_self, sample);
        };
        return wrapper;
    }

};

我用cython包裹的内容:

cdef extern from "wrapper.hpp":
    cdef cppclass function_wrapper[T]:

        ctypedef void (*cy_callback) (void*, shared_ptr[my_cpp_class[T]])

        @staticmethod
        function[void(shared_ptr[my_cpp_class[T]])] make_std_function(
            cy_callback, void*)

基本上,我的想法是将cython类回调方法传递给包装器,并返回带有正确类型签名的std::function版本。返回的函数实际上是一个lambda,它将self参数应用于回调。

现在我可以制作我的cdef成员方法并正确调用它们:

    cdef void member_callback(self, shared_ptr[my_cpp_class[specific_type]] sample) with gil:
        # do stuff with the sample ...
        self.python_cb(sample_related_stuff)

注册看起来像:

register(function_wrapper[specific_type].make_std_function(<cb_type>self.member_callback, <void*>self))

注意那里的cb_type施法者。包装器期待void*,因此我们需要将其转换为匹配。这是typedef:

ctypedef void (*cb_type)(void*, shared_ptr[my_cpp_class[specific_type]])

希望这对类似船上的人有用。感谢@DavidW让我走上正轨。