回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数主要用来为不确定的事件、不确定的时间上进行的动作或响应。比如在C库里实现了一个算法叫做快速排序(qsort),为了能让库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑,这时就可以使用回调函数方式来实现。如msvcrt.dll里的快速排序算法qsort, 我们现在来看一下这个例子,你就会很清楚它的作用:
#windows应用程序
from ctypes import *
from ctypes.wintypes import *
#取得快速排序的函数。
qsort = cdll.msvcrt.qsort;
qsort.restype = None
#定义排序比较函数
def py_cmp_func(a, b):
print("py_cmp_func", a[0], b[0])
return a[0] > b[0]
#转换为C的回调函数
CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
cmp_func = CMPFUNC(py_cmp_func)
#进行排序
num = 5
IntArray5 = c_int * num
ia = IntArray5(5, 1, 7, 33, 99)
#排序前输出数组
for i in ia:
print(i, end = ' ')
print("\nqsort before")
qsort(ia, len(ia), sizeof(c_int), cmp_func)
#排序后输出数组
for i in ia:
print(i, end = ' ')
运行这个例子之后输出如下:
5 1 7 33 99
qsort before
py_cmp_func 1 5
py_cmp_func 7 5
py_cmp_func 33 7
py_cmp_func 99 33
py_cmp_func 1 5
py_cmp_func 7 5
py_cmp_func 33 7
py_cmp_func 1 5
py_cmp_func 7 5
py_cmp_func 1 5
1 5 7 33 99
在这个例子里通过cdll.msvcrt.qsort来获取到msvcrt库的快速排序函数qsort,由于这个函数调用时给一个回调用函数来比较两个元素的大小。因而在后面定义了一个回调函数py_cmp_func,这个回调函数是Python的函数调用格式和类型,并不能直接把函数指针给qsort使用,必须通过ctypes转换才可以。为了这个目的,使用ctypes库里的CFUNCTYPE函数生成一个C语言的回调用函数cmp_func,这时才可以给qsort使用。在C里声明的回调函数的格式如下:
int compare( const void *arg1, const void *arg2 );
也就是说,这个声明CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))与上面这个声明是等效的。
通过运行这个例子之后,我们看到数组里的五个数字已经按从小到大排好顺序出来,达到了回调目的。如果想从大到小排序,只需要改写成下面这个函数:
def py_cmp_func(a, b):
print("py_cmp_func", a[0], b[0])
return a[0] < b[0]
跟原来的函数相比,这里只修改一行代码a[0] < b[0],可见回调函数的威力吧。其实这个例子里只是数值的比较,如果要进行字符串的长度排序,那么就可以修改这个比较函数内容为比较长度即可。如果想要比较所有字符的值加到一起,那个字符串值最大,也可以修改这个比较函数,把每个字符串的字符值累加到一起。
从这里看到以cdecl调用方式的转换函数CFUNCTYPE函数,而想转换为stdcall调用方式,就需要使用WINFUNCTYPE函数。比如声明一个Windows的窗口消息回调函数,就可以像这样编写:
WNDPROCTYPE = WINFUNCTYPE(c_int, HWND, c_uint, WPARAM, LPARAM)
这个函数的声明与下面这个回调函数的声明是一致的:
LRESULT CALLBACK WndProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
因此,在Python里经过WNDPROCTYPE对象的转换,就可以使用于WIN32的API的回调函数里,完美地解决了回调函数的问题。