窗口回调函数,定时器回调函数,都属于回调函数。 一般来讲:回调函数都是基于某种消息驱动,在获取相应消息时调用该函数。 回调函数分系统回调和用户回调。
-----------------------------------------
http://hi.baidu.com/spidermanzy/blog/item/b25b00956469c6097bf48016.html
什么是回调函数?
简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。
为什么要使用回调函数?
因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。
如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。
回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer() API使用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。
另一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上所有的顶层窗口,为每个窗口调用一个程序提供的函数,并传递窗口的处理程序。如果被调用者返回一个值,就继续进行迭代,否则,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。
不管怎么说,回调函数是继续自C语言的,因而,在C++中,应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。
一个简单的回调函数实现
下面创建了一个sort.dll的动态链接库,它导出了一个名为CompareFunction的类型--typedef int (__stdcall *CompareFunction)(const byte*, const byte*),它就是回调函数的类型。另外,它也导出了两个方法:Bubblesort()和Quicksort(),这两个方法原型相同,但实现了不同的排序算法。
void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc); void DLLDIR __stdcall Quicksort(byte* array,int size,int elem_size,CompareFunction cmpFunc); |
·byte * array:指向元素数组的指针(任意类型)。
·int size:数组中元素的个数。
·int elem_size:数组中一个元素的大小,以字节为单位。
·CompareFunction cmpFunc:带有上述原型的指向回调函数的指针。
这两个函数的会对数组进行某种排序,但每次都需决定两个元素哪个排在前面,而函数中有一个回调函数,其地址是作为一个参数传递进来的。 对编写者来说,不必介意函数在何处实现,或它怎样被实现的,所需在意的只是两个用于比较的元素的地址,并返回以下的某个值(库的编写者和使用者都必须遵守这个约定):
·-1:如果第一个元素较小,那它在已排序好的数组中,应该排在第二个元素前面。
·0:如果两个元素相等,那么它们的相对位置并不重要,在已排序好的数组中,谁在前面都无所谓。
·1:如果第一个元素较大,那在已排序好的数组中,它应该排第二个元素后面。
基于以上约定,函数Bubblesort()的实现如下,Quicksort()就稍微复杂一点:
void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc) { for(int i=0; i < size; i++) { for(int j=0; j < size-1; j++) { //回调比较函数 if(1 == (*cmpFunc)(array+j*elem_size,array+(j+1)*elem_size)) { //两个相比较的元素相交换 byte* temp = new byte[elem_size]; memcpy(temp, array+j*elem_size, elem_size); memcpy(array+j*elem_size,array+(j+1)*elem_size,elem_size); memcpy(array+(j+1)*elem_size, temp, elem_size); delete [] temp; } } } } |
对使用者来说,必须有一个回调函数,其地址要传递给Bubblesort()函数。下面有二个简单的示例,一个比较两个整数,而另一个比较两个字符串:
int __stdcall CompareInts(const byte* velem1, const byte* velem2) { int elem1 = *(int*)velem1; int elem2 = *(int*)velem2; if(elem1 < elem2) return -1; if(elem1 > elem2) return 1; return 0; } int __stdcall CompareStrings(const byte* velem1, const byte* velem2) { const char* elem1 = (char*)velem1; const char* elem2 = (char*)velem2; return strcmp(elem1, elem2); } |
下面另有一个程序,用于测试以上所有的代码,它传递了一个有5个元素的数组给Bubblesort()和Quicksort(),同时还传递了一个指向回调函数的指针。
int main(int argc, char* argv[]) { int i; int array[] = {5432, 4321, 3210, 2109, 1098}; cout << "Before sorting ints with Bubblesort/n"; for(i=0; i < 5; i++) cout << array[i] << '/n'; Bubblesort((byte*)array, 5, sizeof(array[0]), &CompareInts); cout << "After the sorting/n"; for(i=0; i < 5; i++) cout << array[i] << '/n'; const char str[5][10] = {"estella","danielle","crissy","bo","angie"}; cout << "Before sorting strings with Quicksort/n"; for(i=0; i < 5; i++) cout << str[i] << '/n'; Quicksort((byte*)str, 5, 10, &CompareStrings); cout << "After the sorting/n"; for(i=0; i < 5; i++) cout << str[i] << '/n'; return 0; } |
如果想进行降序排序(大元素在先),就只需修改回调函数的代码,或使用另一个回调函数,这样编程起来灵活性就比较大了。
调用约定
上面的代码中,可在函数原型中找到__stdcall,因为它以双下划线打头,所以它是一个特定于编译器的扩展,说到底也就是微软的实现。任何支持开发基于Win32的程序都必须支持这个扩展或其等价物。以__stdcall标识的函数使用了标准调用约定,为什么叫标准约定呢,因为所有的Win32 API(除了个别接受可变参数的除外)都使用它。标准调用约定的函数在它们返回到调用者之前,都会从堆栈中移除掉参数,这也是Pascal的标准约定。但在C/C++中,调用约定是调用者负责清理堆栈,而不是被调用函数;为强制函数使用C/C++调用约定,可使用__cdecl。另外,可变参数函数也使用C/C++调用约定。
Windows操作系统采用了标准调用约定(Pascal约定),因为其可减小代码的体积。这点对早期的Windows来说非常重要,因为那时它运行在只有640KB内存的电脑上。
如果你不喜欢__stdcall,还可以使用CALLBACK宏,它定义在windef.h中:
#define CALLBACK __stdcallor #define CALLBACK PASCAL //而PASCAL在此被#defined成__stdcall |
作为回调函数的C++方法
因为平时很可能会使用到C++编写代码,也许会想到把回调函数写成类中的一个方法,但先来看看以下的代码:
class CCallbackTester { public: int CALLBACK CompareInts(const byte* velem1, const byte* velem2); }; Bubblesort((byte*)array, 5, sizeof(array[0]), &CCallbackTester::CompareInts); |
error C2664: 'Bubblesort' : cannot convert parameter 4 from 'int (__stdcall CCallbackTester::*)(const unsigned char *,const unsigned char *)' to 'int (__stdcall *)(const unsigned char *,const unsigned char *)' There is no context in which this conversion is possible |
---------------------------------------------------------
http://learn.akae.cn/media/ch24s05.html(Linux C教程)
5. 回调函数 请点评
如果参数是一个函数指针,调用者可以传递一个函数的地址给实现者,让实现者去调用它,这称为回调函数。
表 24.7. 回调函数示例:void func(void (*f)(void *), void *p);
调用者 | 实现者 |
---|---|
|
|
以下是一个简单的例子。实现了一个repeat_three_times
函数,可以把调用者传来的任何回调函数连续执行三次。
例 24.7. 回调函数
/* para_callback.h */ #ifndef PARA_CALLBACK_H #define PARA_CALLBACK_H typedef void (*callback_t)(void *); extern void repeat_three_times(callback_t, void *); #endif
/* para_callback.c */ #include "para_callback.h" void repeat_three_times(callback_t f, void *para) { f(para); f(para); f(para); }
/* main.c */ #include <stdio.h> #include "para_callback.h" void say_hello(void *str) { printf("Hello %s/n", (const char *)str); } void count_numbers(void *num) { int i; for(i=1; i<=(int)num; i++) printf("%d ", i); putchar('/n'); } int main(void) { repeat_three_times(say_hello, "Guys"); repeat_three_times(count_numbers, (void *)4); return 0; }
回顾一下前面几节的例子,参数类型都是由实现者规定的。而本例中回调函数的参数按什么类型解释由调用者规定,对于实现者来说就是一个void *
指针,实现者只负责将这个指针转交给回调函数,而不关心它到底指向什么数据类型。调用者知道自己传的参数是char *
型的,那么在自己提供的回调函数中就应该知道参数要转换成char *
型来解释。
回调函数的一个典型应用就是实现类似C++的泛型算法(Generics Algorithm)。下面实现的max
函数可以在任意一组对象中找出最大值,可以是一组int
、一组char
或者一组结构体,但是实现者并不知道怎样去比较两个对象的大小,调用者需要提供一个做比较操作的回调函数。
例 24.8. 泛型算法
/* generics.h */ #ifndef GENERICS_H #define GENERICS_H typedef int (*cmp_t)(void *, void *); extern void *max(void *data[], int num, cmp_t cmp); #endif
/* generics.c */ #include "generics.h" void *max(void *data[], int num, cmp_t cmp) { int i; void *temp = data[0]; for(i=1; i<num; i++) { if(cmp(temp, data[i])<0) temp = data[i]; } return temp; }
/* main.c */ #include <stdio.h> #include "generics.h" typedef struct { const char *name; int score; } student_t; int cmp_student(void *a, void *b) { if(((student_t *)a)->score > ((student_t *)b)->score) return 1; else if(((student_t *)a)->score == ((student_t *)b)->score) return 0; else return -1; } int main(void) { student_t list[4] = {{"Tom", 68}, {"Jerry", 72}, {"Moby", 60}, {"Kirby", 89}}; student_t *plist[4] = {&list[0], &list[1], &list[2], &list[3]}; student_t *pmax = max((void **)plist, 4, cmp_student); printf("%s gets the highest score %d/n", pmax->name, pmax->score); return 0; }
max
函数之所以能对一组任意类型的对象进行操作,关键在于传给max
的是指向对象的指针所构成的数组,而不是对象本身所构成的数组,这样max
不必关心对象到底是什么类型,只需转给比较函数cmp
,然后根据比较结果做相应操作即可,cmp
是调用者提供的回调函数,调用者当然知道对象是什么类型以及如何比较。
以上举例的回调函数是被同步调用的,调用者调用max
函数,max
函数则调用cmp
函数,相当于调用者间接调了自己提供的回调函数。在实际系统中,异步调用也是回调函数的一种典型用法,调用者首先将回调函数传给实现者,实现者记住这个函数,这称为注册一个回调函数,然后当某个事件发生时实现者再调用先前注册的函数,比如sigaction(2)
注册一个信号处理函数,当信号产生时由系统调用该函数进行处理,再比如pthread_create(3)
注册一个线程函数,当发生调度时系统切换到新注册的线程函数中运行,在GUI编程中异步回调函数更是有普遍的应用,例如为某个按钮注册一个回调函数,当用户点击按钮时调用它。
以下是一个代码框架。
/* registry.h */ #ifndef REGISTRY_H #define REGISTRY_H typedef void (*registry_t)(void); extern void register_func(registry_t); #endif
/* registry.c */ #include <unistd.h> #include "registry.h" static registry_t func; void register_func(registry_t f) { func = f; } static void on_some_event(void) { ... func(); ... }
既然参数可以是函数指针,返回值同样也可以是函数指针,因此可以有func()();
这样的调用。返回函数的函数在C语言中很少见,在一些函数式编程语言(例如LISP)中则很常见,基本思想是把函数也当作一种数据来操作,输入、输出和参与运算,操作函数的函数称为高阶函数(High-order Function)。
--------------------------------------------------
http://www.china-askpro.com/msg25/qa03.shtml
回调函数的内在机制如何
Q 刘虎翼:
编程工具: C++ BUILDER 3.0
操作系统: WIN98
我想在C++ 中使用回调函数,请问它的内在机制如何,另外怎么定义。我用DialogBox函数时,如何使用回调函数? 它和钩子函数有何不同?多谢指教!!!拜托!!!
A回答:
使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。至于如何定义回调函数,跟具体使用的API函数有关,一般在帮助中有说明回调函数的参数和返回值等。C++中一般要求在回调函数前加CALLBACK,这主要是说明该函数的调用方式。DialogBox的回调函数实际上是个窗口过程,用来处理所有消息。其定义为:
BOOL CALLBACK DialogProc(
HWND hwndDlg, // handle of dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
在Win32 API中有详细说明。一般使用C++ Builder或MFC的往往没有使用SDK编程的经验,建议找一些SDK编程的书看一下,否则很难理解如何使用窗口过程。
至于钩子函数,只是回调函数的一个特例。习惯上把与SetWindowsHookEx函数一起使用的回调函数称为钩子函数。也有人把利用VirtualQueryEx安装的函数称为钩子函数,不过这种叫法不太流行。
frank的意见:
我对回调函数的理解虽然粗浅,但是我觉得会让人更容易理解:回调函数就相当于一个中断处理函数,由系统在符合你设定的条件时自动调用。为此,你需要做三件事:1,声明;2,定义;3,设置触发条件,就是在你的函数中把你的回调函数名称转化为地址作为一个参数,以便于系统调用。
声明和定义时应注意:回调函数由系统调用,所以可以认为它属于WINDOWS系统。不要把它当作你的某个类的成员函数。
ping的意见:
frank说:回调函数属于WINDOWS系统。我觉得不应该说回调函数是属于系统的。应该说是程序把这段代码的触发交由系统来做。而这种做法是WINDOWS提供的处理机制吧,因为消息是系统一手掌握着的,由系统来调用我们的程序对消息的处理部分,这样子会比较方便。不然我们又得花力气去读消息列表了。(不知道我说的对不对,接触系统还不深,请高手指教哦)