Cython - 实现回调函数

11

我一直在尝试使用Cython来接口一个用C++编写的库。到目前为止,情况还不错,我可以有效地使用库中的大多数函数。我的唯一问题在于实现回调函数。该库有4个函数定义,看起来有点像这样:

typedef void (*Function1)(const uint16_t *data, 
                         unsigned width, unsigned height);
void SetCallBack(Function1);

因此,为了实现它们,我想使用cython来做类似于这样的事情:

ctypedef void (*Function1)(unsigned short *data, 
                         unsigned width, unsigned height);
cdef extern from "lib.hpp":
    void SetCallBack(Function1)

这段代码实际上可以正确编译,但是我却无法想出如何以使回调函数起作用的方式来实现它。我首先尝试创建一个函数来调用它,类似于你为任何其他函数所做的操作,得到了以下结果:

def PySetCallBack(Func):
    SetCallBack(Func)

但是这给了我一个(可以预料的)错误:

"无法将Python对象转换为'Function1'"

所以,这就是我的问题所在。如果有人在Cython中设置回调方面有任何经验,我将非常感激您提供的任何帮助。谢谢。

编辑:根据您的建议,我创建了一个带有cdef的中间函数,它看起来像这样:

cdef void cSetCallBack(Function1 function):
    SetCallBack(function)

这似乎让我...更接近了?至少现在出现了不同的错误:
error: invalid conversion from ‘void (*)(short unsigned int*, unsigned int, unsigned int)’ to ‘void (*)(const uint16_t*, unsigned int, unsigned int)’

今日免费次数已满, 请开通会员/明日再来
ctypedef unsigned short uint16_t

使用这个作为调用的参数,但显然这并没有使我更接近解决问题,而只是把我带到了一个岔路口,因为在尝试调用该函数时,我又一次收到了“无法将Python对象转换为'Function1'”的错误信息。
所以,我现在基本上回到了起点。现在唯一能想到的就是显式地将输入的Python对象强制转换成C函数,但说实话,我不知道该怎么做。
第三次编辑: 好吧,在分析了你的答案之后,我终于明白了,并且它也能够工作,所以欢呼一下。我最终做的事情是创建一个像这样的函数:
cdef void cSetCallback(Function1 function):
    SetCallback(function)
cdef void callcallback(const_ushort *data, unsigned width, unsigned height):
    global callbackfunc
    callbackfunc(data,width,height)
cSetCallback(callcallback)
def PySetCallback(callbackFunc):
    global callbackfunc
    callbackfunc = callbackFunc

现在唯一的问题是无法将const_ushort * data转换为Python对象,但这完全是另一个问题,所以我想这个问题已经解决了,非常感谢。


short unsigned int* 更改为 const short unsigned int* - aschepler
1
据我所知,Cython 不知道 const 的含义,每当我尝试使用它时,都会出现“Const 不是类型标识符”的错误。 - Josiah
2个回答

9

最近,我也遇到了一个情况,需要使用Cython将现有的C++库与Python进行接口交互,并且密集地使用事件/回调功能。找到相关资料并整理这些内容并不容易,因此我希望在这里把它们归纳总结:

首先,我们需要封装一个C++回调类(基于“double (METHOD)(void)”原型,但可以使用模板,因为Cython可以处理模板):

ALabCallBack.h:

#ifndef ALABCALLBACK_H_
#define ALABCALLBACK_H_


#include <iostream>

using namespace std;

namespace elps {

//template < typename ReturnType, typename Parameter >
class ALabCallBack {
public:

    typedef double (*Method)(void *param, void *user_data);

    ALabCallBack();
    ALabCallBack(Method method, void *user_data);
    virtual ~ALabCallBack();

    double cy_execute(void *parameter);

    bool IsCythonCall()
    {
        return is_cy_call;
    }

protected:

    bool is_cy_call;

private:

    //void *_param;
    Method _method;
    void *_user_data;

};


} /* namespace elps */
#endif /* ALABCALLBACK_H_ */

ALabCallBack.cpp :

#include "ALabCallBack.h"

namespace elps {


ALabCallBack::ALabCallBack() {
    is_cy_call = true;
};

ALabCallBack::~ALabCallBack() {
};

ALabCallBack::ALabCallBack(Method method, void *user_data) {
    is_cy_call = true;
    _method = method;
    _user_data = user_data;
};

double ALabCallBack::cy_execute(void *parameter)
{
    return _method(parameter, _user_data);
};


} /* namespace elps */

何时使用:

  • 'callback' :: 从C类型信息中触发Python(=Method)对象方法的模式/转换器方法

  • 'method' :: Python用户(=user_data)传递的有效方法

  • 'parameter' :: 传递给'method'的参数

现在,我们需要实现pyx文件...

我们的基本原型:

ctypedef double (*Method)(void *param, void *user_data)

然后,我们为C ++类提供了Cython包装器:

cdef extern from "../inc/ALabCallBack.h" namespace "elps" :
    cdef cppclass ALabCallBack:
        ALabCallBack(Method method, void *user_data)
        double cy_execute(void *parameter)

将C类型原型翻译为Python对象调用的模式/转换方法:

cdef double callback(void *parameter, void *method):
    return (<object>method)(<object>parameter)

现在让我们将这些功能嵌入到Cython类中:
cdef class PyLabCallBack:
    cdef ALabCallBack* thisptr

    def __cinit__(self, method):
        # 'callback' :: The pattern/converter method to fire a Python 
        #               object method from C typed infos
        # 'method'   :: The effective method passed by the Python user 
       self.thisptr = new ALabCallBack(callback, <void*>method)

    def __dealloc__(self):
       if self.thisptr:
           del self.thisptr

    cpdef double execute(self, parameter):
        # 'parameter' :: The parameter to be passed to the 'method'
        return self.thisptr.cy_execute(<void*>parameter)

编辑:改进execute函数的输入方式:def execute => cpdef double

就这样。调用它就像这样:

def func(obj):
    print obj 
    obj.Test()     # Call to a specific method from class 'PyLabNode'
    return obj.d_prop

n = PyLabNode()    # Custom class of my own
cb = PyLabCallBack(func)
print cb.execute(n)

作为一种隐式类型的语言,Python 可以在需要触发回调时访问与传递参数对象类相关的属性。
这段代码可以很容易地适用于纯 C 实现。如果您认为有任何可能的改进方法,请告诉我(在我的情况下,性能非常关键,因为事件频繁触发)。

5
如果您可以修改库以定义:
typedef void (*Function1)(const uint16_t *data, 
                          unsigned width, unsigned height,
                          void *user_data);
void SetCallBack(Function1, void*);

相反,我担心你没有运气。如果你有void*,那么你需要定义一个函数来正确地调用Python可调用对象并使用此函数和Python可调用对象调用SetCallBack
如果你不能这样做,但回调是全局的(似乎是这样),你可以创建一个全局变量来存储Python对象。然后,您将再次创建一个函数来调用Python对象并将其传递给SetCallBack,而您的PySetCallback将只设置全局变量并确保已注册正确的函数。
如果回调是上下文特定的,但您没有办法传递“用户数据”指针,则在这里我恐怕你没有运气。
我知道Python和C ++,但不知道Cython,因此我不知道您是否可以在Cython中创建该函数,或者您是否必须编写C ++。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接