Cython - 实现回调

Posted

技术标签:

【中文标题】Cython - 实现回调【英文标题】:Cython - implementing callbacks 【发布时间】:2011-03-09 06:09:49 【问题描述】:

我一直在与 Cython 合作,试图与用 c++ 编写的库进行交互。到目前为止,一切进展顺利,我可以有效地使用库中的 MOST 函数。我唯一的问题在于实现回调。该库有 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)’

现在,据我所知,这些类型是相同的,所以我不知道发生了什么。

编辑2: 通过声明一个新的 typedef 来解决这个问题:

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* 据我所知,Cython 不知道 const 是什么意思,每当我尝试使用它时,都会得到“Const is not a type identifier”。 【参考方案1】:

我最近遇到的情况是,我还必须使用 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' :: 触发 Python 的模式/转换器方法 (=Method) 来自 C 类型信息的对象方法

'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)

编辑:更好的执行功能输入: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 是隐式类型的,当触发回调时,我们可以访问与作为参数传递的对象的类相关的“obj”对象的属性。

它可以很容易地适应纯 C 实现。 请告诉我您是否可以看到任何可能的改进(在我的情况下,性能非常重要,因为事件被密集触发)。

【讨论】:

【参考方案2】:

如果可以修改库来定义:

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++编写。

【讨论】:

以上是关于Cython - 实现回调的主要内容,如果未能解决你的问题,请参考以下文章

使用回调将 C 库 (GSL) 包装在 cython 代码中

Cython:具有自定义参数类型的 std::function 回调

使用 python-c-api 调用的 Cython 回调段错误

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

Cython:不公开部分实现

Cython 相关