Cython - 实现回调函数
我一直在使用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)’
现在,按照我的理解,这些类型是相同的,所以我搞不清楚发生了什么。
编辑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对象,但那是另一个完全不同的问题,所以我想这个问题解决了,非常感谢。
2 个回答
最近,我遇到了一个需要把现有的C++库和Python结合起来的情况,使用了Cython,并且大量使用了事件和回调。找到相关资料并不容易,所以我想把这些内容整理在这里:
首先,我们需要包装一个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)
编辑:对执行函数的类型进行更好的定义: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的实现。如果你能看到任何可能的改进,请告诉我(在我的情况下,性能非常关键,因为事件被频繁触发)。
如果你能修改这个库来定义:
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++来写。