C++回调函数用法
副标题#e#
一回调函数
我们常常在C++设计时通过利用回调函数可以使有些应用(如按时器事件回调处理惩罚、用回调函数记录某操纵进度等)变得很是利便和切合逻辑,那么它的内涵机制如何呢,怎么界说呢?它和其它函数(好比钩子函数)有何差异呢?
利用回调函数实际上就是在挪用某个函数(凡是是API函数)时,将本身的一个函数(这个函数为回调函数)的地点作为参数通报给谁人函数。
而 谁人函数在需要的时候,操作通报的地点挪用回调函数,这时你可以操作这个时机在回调函数中处理惩罚动静或完成必然的操纵。至于如何界说回调函数,跟详细利用的 API函数有关,一般在辅佐中有说明回调函数的参数和返回值等。C++中一般要求在回调函数前加CALLBACK(相当于FAR PASCAL),这主要是说明该函数的挪用方法。
至于钩子函数,只是回调函数的一个特例。习惯上把与SetWindowsHookEx函数一起利用的回调函数称为钩子函数。也有人把操作VirtualQueryEx安装的函数称为钩子函数,不外这种叫法不太风行。
也可以这样,更容易领略:回调函数就仿佛是一其间断处理惩罚函数,系统在切合你设定的条件时自动挪用。为此,你需要做三件事:
1. 声明;
2. 界说;
3. 配置触发条件,就是在你的函数中把你的回调函数名称转化为地点作为一个参数,以便于系统挪用。
声明和界说时应留意:回调函数由系统挪用,所以可以认为它属于WINDOWS系统,不要把它看成你的某个类的成员函数。
二回调函数、动静和事件例程
挪用(calling)机制从汇编时代起已经大量利用:筹备一段现成的代码,挪用者可以随时跳转至此段代码的起始地点,执行完后再返回跳转时的后续地点。 CPU为此筹备了现成的挪用指令,挪用时可以压栈掩护现场,挪用竣事后从仓库中弹呈现园地点,以便自动返回。借仓库掩护现场真是一项绝妙的发现,它使挪用 者和被调者可以互不领会,于是才有了厥后的函数和构件。
此挪用机制并非完美。回调函数就是一例。函数之类本是为挪用者筹备的美餐,其烹制者应对食客洞若观火,但实情并非如此。譬喻,写一个快速排序函数供他人调 用,个中必包括较量巨细。贫苦来了:此时并不知要较量的是何类数据–整数、浮点数、字符串?于是只好为每类数据建造一个差异的排序函数。更通行的步伐是 在函数参数中列一个回调函数地点,并通知挪用者:君需本身筹备一个较量函数,个中包括两个指针类参数,函数要较量此二指针所指数据之巨细,并由函数返回值 说明较量功效。排序函数借此挪用者提供的函数来较量巨细,借指针通报参数,可以全然不管所较量的数据范例。被挪用者转头挪用挪用者的函数(够咬嘴的),故 称其为回调(callback)。
回调函数使措施布局乱了很多。Windows API 函数会合有不少回调函数,尽量有详尽说明,仍使初学者一头雾水。恐怕这也是无奈之举。
无论何种事物,能以树形布局单向描写究竟让人舒服些。假如某家属中孙辈又是某祖辈的祖辈,恐怕无人能理清个中的头绪。但数据处理惩罚之巨大往往需要组成网状布局,非简朴的客户/处事器干系能穷尽。
Windows 系统还包括着另一种更为遍及的回调机制,即动静机制。动静本是 Windows 的根基节制手段,乍看与函数挪用无关,其实是一种变相的函数挪用。发送动静的目标是通知收方运行一段预先筹备好的代码,相当于挪用一个函数。动静所附带的 WParam 和 LParam 相当于函数的参数,只不外比普通参数更通用一些。应用措施可以主动发送动静,更多环境下是坐等 Windows 发送动静。一旦动静进入所属动静行列,便检感乐趣的那些,跳转去执行相应的动静处理惩罚代码。操纵系统本是为应用措施处事,由应用措施来挪用。而应用措施一旦 启动,却要反过来期待操纵系统的挪用。这理解也是一种回调,可能说是一种广义回调。其实,应用措施之间也可以形成这种回调。如果历程 B 收到历程 A 发来的动静,启动了一段代码,个中又向历程 A 发送动静,这就形成了回调。这种回调较量隐蔽,弄欠好会搞成递归挪用,若缺少终止条件,将会轮回不已,直至把措施搞垮。若是存心编写成此递归挪用,并设好 终止条件,倒是很有意思。但这种措施布局太隐蔽,除非十分须要,照旧不消为好。
操作动静也可以组成狭义回调。上面所举排序函数一例,可以把回调函数地点换成窗口 handle。如此,当需要较量数据巨细时,不是去挪用回调函数,而是借 API 函数 SendMessage 向指定窗口发送动静。收到动静方认真较量数据巨细,把较量功效通过动静自己的返回值传给动静发送方。所实现的成果与回调函数并无差异。虽然,此例中改为消 息纯属画蛇添脚,反倒把措施搞得很慢。但其他环境下并非老是如此,出格是需要异法式用时,发送动静是一种不错的选择。如果回调函数中包括文件处理惩罚之类的低 速处理惩罚,挪用方等不得,需要把同法式用改为异法式用,去启动一个单独的线程,然后顿时执行后续代码,其余的事让线程逐步去做。一个替代步伐是借 API 函数 PostMessage 发送一个异步动静,然后当即执行后续代码。这要比本身搞个线程省事很多,并且更安详。
#p#分页标题#e#
如今我们是活在一个 object 时代。只要与编程有关,无论何事都离不开 object。但 object 并未消除回调,反而把它发扬光大,弄得处处都是,只不外多半以事件(event)的身份呈现,镶嵌在某个布局之中,显得矫正统,更容易被人接管。应用措施 要利用某个构件,总要先弄清构件的属性、要领和事件,然后给构件属性赋值,在适当的时候挪用适当的构件要领,还要给事件编写处理惩罚例程,以备构件代码来调 用。何谓事件?它不外是一个指向事件例程的地点,与回调函数地点没什么区别。
不外,此种回调方法比传统回调函数要高超很多。首先,它把让人不太舒服的回调函数酿成一种自然而然的处理惩罚例程,使编程者顿觉气顺。再者,地点是一个危险的 对象,用好了可使措施加快,用欠好随处是陷阱,措施随时城市瓦解。现代编程方法老是想法把地点埋没起来(埋没较量彻底的如 VB 和 Java),其价钱是低落了措施效率。事件例程(?)使编程者无需直接操纵地点,但并不会使措施减速。
(例程好像是历程的台湾翻译。)
三精妙比喻:回调函数还真有点像您随身带的BP机:汇报别人号码,在它有工作时Call您。
回挪用于层间协作,上层将本层函数安装在基层,这个函数就是回调,而基层在必然条件下触发回调,譬喻作为一个驱动,是一个底层,他在收到一个数据时,除了 完本钱层的处理惩罚事情外,还将举办回调,将这个数据交给上层应用层来做进一步处理惩罚,这在分层的数据通信中很普遍。其实回和谐API很是靠近,他们的共性都是 跨层挪用的函数。但区别是API是低层提供应高层的挪用,一般这个函数对高层都是已知的;而回调正好相反,他是高层提供应底层的挪用,对付低层他是未知 的,必需由高层举办安装,这个安装函数其实就是一个低层提供的API,安装后低层不知道这个回调的名字,但它通过一个函数指针来生存这个回调,在需要挪用 时,只需引用这个函数指针和相关的参数指针。 其实:回调就是该函数写在高层,低层通过一个函数指针生存这个函数,在某个事件的触发下,低层通过该函数指针挪用高层谁人函数。
#p#副标题#e#
四挪用方法
软件模块之间老是存在着必然的接口,从挪用方法上,可以把他们分为三类:同法式用、回和谐异法式用。同法式用是一种阻塞式挪用,挪用方要期待对方执行完毕 才返回,它是一种单向挪用;回调是一种双向挪用模式,也就是说,被挪用方在接口被挪用时也会挪用对方的接口;异法式用是一种雷同动静或事件的机制,不外它 的挪用偏向恰好相反,接口的处事在收到某种讯息或产生某种事件时,会主动通知客户方(即挪用客户方的接口)。回和谐异法式用的干系很是细密,凡是我们利用 回调来实现异步动静的注册,通过异法式用来实现动静的通知。同法式用是三者傍边最简朴的,而回调又经常是异法式用的基本。
对付差异范例的语言(如布局化语言和工具语言)、平台(Win32、JDK)或构架(CORBA、DCOM、WebService),客户和处事的交互除 了同步方法以外,都需要具备必然的异步通知机制,让处事方(或接口提供方)在某些环境下可以或许主动通知客户,而回调是实现异步的一个最简便的途径。
对付一般的布局化语言,可以通过回调函数来实现回调。回调函数也是一个函数或进程,不外它是一个由挪用方本身实现,供被挪用方利用的非凡函数。
在面向工具的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的工具成为回调工具。对付象C++或Object Pascal这些兼容了进程特性的工具语言,不只提供了回调工具、回调要领等特性,也能兼容进程语言的回调函数机制。
Windows平台的动静机制也可以看作是回调的一种应用,我们通过系统提供的接口注册动静处理惩罚函数(即回调函数),从而实现吸收、处理惩罚动静的目标。由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。
#p#分页标题#e#
对付漫衍式组件署理体系CORBA,异步处理惩罚有多种方法,如回调、事件处事、通知处事等。事件处事和通知处事是CORBA用来处理惩罚异步动静的尺度处事,他们主要认真动静的处理惩罚、派发、维护等事情。对一些简朴的异步处理惩罚进程,我们可以通过回调机制来实现。
下面我们会合较量具有代表性的语言(C、Object Pascal)和架构(CORBA)来阐明回调的实现方法、详细浸染等。
进程语言中的回调(C)
(1 )函数指针
回调在C语言中是通过函数指针来实现的,通过将回调函数的地点传给被调函数从而实现回调。因此,要实现回调,必需首先界说函数指针,请看下面的例子:
void Func(char *s);// 函数原型
void (*pFunc) (char *);//函数指针
可以看出,函数的界说和函数指针的界说很是雷同。
一般的化,为了简化函数指针范例的变量界说,提高措施的可读性,我们需要把函数指针范例自界说一下。
typedef void(*pcb)(char *);
回调函数可以象普通函数一样被措施挪用,可是只有它被看成参数通报给被调函数时才气称作回调函数。
被调函数的例子:
void GetCallBack(pcb callback)
{
/*do something*/
}
用户在挪用上面的函数时,需要本身实现一个pcb范例的回调函数:
void fCallback(char *s)
{
/* do something */
}
然后,就可以直接把fCallback看成一个变量通报给GetCallBack,
GetCallBack(fCallback);
假如赋了差异的值给该参数,那么挪用者将挪用差异地点的函数。赋值可以产生在运行时,这样使你能实现动态绑定。
(2 )参数通报法则
到今朝为止,我们只接头了函数指针及回调而没有去留意ANSI C/C++的编译器类型。很多编译器有几种挪用类型。如在Visual C++中,可以在函数范例前加_cdecl,_stdcall可能_pascal来暗示其挪用类型(默认为_cdecl)。C++ Builder也支持_fastcall挪用类型。挪用类型影响编译器发生的给定函数名,参数通报的顺序(从右到左或从左到右),仓库清理责任(挪用者或 者被挪用者)以及参数通报机制(仓库,CPU寄存器等)。
将挪用类型当作是函数范例的一部门是很重要的;不能用不兼容的挪用类型将地点赋值给函数指针。譬喻:
// 被挪用函数是以int为参数,以int为返回值
__stdcall int callee(int);
// 挪用函数以函数指针为参数
void caller( __cdecl int(*ptr)(int));
// 在p中诡计存储被挪用函数地点的犯科操纵
__cdecl int(*p)(int) = callee; // 堕落
指针p和callee()的范例不兼容,因为它们有差异的挪用类型。因此不能将被挪用者的地点赋值给指针p,尽量两者有沟通的返回值和参数列
(3 )应用举例
C语言的尺度库函数中许多处所就回收了回调函数来让用户定制处理惩罚进程。如常用的快速排序函数、二分搜索函数等。
快速排序函数原型:
void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
二分搜索函数原型:
void *bsearch(const void *key, const void *base, size_t nelem,
size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
个中fcmp就是一个回调函数的变量。
下面给出一个详细的例子:
#include <stdio.h>
#include <stdlib.h>
int sort_function( const void *a, const void *b);
int list[5] = { 54, 21, 11, 67, 22 };
int main(void)
{
int x;
qsort((void *)list, 5, sizeof(list[0]), sort_function);
for (x = 0; x < 5; x++)
printf("%i\n", list[x]);
return 0;
}
int sort_function( const void *a, const void *b)
{
return *(int*)a-*(int*)b;
}