关于回调函数.docx
- 文档编号:30745749
- 上传时间:2023-08-20
- 格式:DOCX
- 页数:13
- 大小:25.66KB
关于回调函数.docx
《关于回调函数.docx》由会员分享,可在线阅读,更多相关《关于回调函数.docx(13页珍藏版)》请在冰豆网上搜索。
关于回调函数
关于回调函数
一,回调函数
我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?
它和其它函数(比如钩子函数)有何不同呢?
使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。
而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。
至于如何定义回调函数,跟具体使用的API函数有关,一般在帮助中有说明回调函数的参数和返回值等。
C++中一般要求在回调函数前加CALLBACK(相当于FARPASCAL),这主要是说明该函数的调用方式。
至于钩子函数,只是回调函数的一个特例。
习惯上把与SetWindowsHookEx函数一起使用的回调函数称为钩子函数。
也有人把利用VirtualQueryEx安装的函数称为钩子函数,不过这种叫法不太流行。
也可以这样,更容易理解:
回调函数就好像是一个中断处理函数,系统在符合你设定的条件时自动调用。
为此,你需要做三件事:
1.声明;
2.定义;
3.设置触发条件,就是在你的函数中把你的回调函数名称转化为地址作为一个参数,以便于系统调用。
声明和定义时应注意:
回调函数由系统调用,所以可以认为它属于WINDOWS系统,不要把它当作你的某个类的成员函数。
二,回调函数、消息和事件例程
调用(calling)机制从汇编时代起已经大量使用:
准备一段现成的代码,调用者可以随时跳转至此段代码的起始地址,执行完后再返回跳转时的后续地址。
CPU为此准备了现成的调用指令,调用时可以压栈保护现场,调用结束后从堆栈中弹出现场地址,以便自动返回。
借堆栈保护现场真是一项绝妙的发明,它使调用者和被调者可以互不相识,于是才有了后来的函数和构件。
此调用机制并非完美。
回调函数就是一例。
函数之类本是为调用者准备的美餐,其烹制者应对食客了如指掌,但实情并非如此。
例如,写一个快速排序函数供他人调用,其中必包含比较大小。
麻烦来了:
此时并不知要比较的是何类数据--整数、浮点数、字符串?
于是只好为每类数据制作一个不同的排序函数。
更通行的办法是在函数参数中列一个回调函数地址,并通知调用者:
君需自己准备一个比较函数,其中包含两个指针类参数,函数要比较此二指针所指数据之大小,并由函数返回值说明比较结果。
排序函数借此调用者提供的函数来比较大小,借指针传递参数,可以全然不管所比较的数据类型。
被调用者回头调用调用者的函数(够咬嘴的),故称其为回调(callback)。
回调函数使程序结构乱了许多。
WindowsAPI函数集中有不少回调函数,尽管有详尽说明,仍使初学者一头雾水。
恐怕这也是无奈之举。
无论何种事物,能以树形结构单向描述毕竟让人舒服些。
如果某家族中孙辈又是某祖辈的祖辈,恐怕无人能理清其中的头绪。
但数据处理之复杂往往需要构成网状结构,非简单的客户/服务器关系能穷尽。
Windows系统还包含着另一种更为广泛的回调机制,即消息机制。
消息本是Windows的基本控制手段,乍看与函数调用无关,其实是一种变相的函数调用。
发送消息的目的是通知收方运行一段预先准备好的代码,相当于调用一个函数。
消息所附带的WParam和LParam相当于函数的参数,只不过比普通参数更通用一些。
应用程序可以主动发送消息,更多情况下是坐等Windows发送消息。
一旦消息进入所属消息队列,便检感兴趣的那些,跳转去执行相应的消息处理代码。
操作系统本是为应用程序服务,由应用程序来调用。
而应用程序一旦启动,却要反过来等待操作系统的调用。
这分明也是一种回调,或者说是一种广义回调。
其实,应用程序之间也可以形成这种回调。
假如进程B收到进程A发来的消息,启动了一段代码,其中又向进程A发送消息,这就形成了回调。
这种回调比较隐蔽,弄不好会搞成递归调用,若缺少终止条件,将会循环不已,直至把程序搞垮。
若是故意编写成此递归调用,并设好终止条件,倒是很有意思。
但这种程序结构太隐蔽,除非十分必要,还是不用为好。
利用消息也可以构成狭义回调。
上面所举排序函数一例,可以把回调函数地址换成窗口handle。
如此,当需要比较数据大小时,不是去调用回调函数,而是借API函数SendMessage向指定窗口发送消息。
收到消息方负责比较数据大小,把比较结果通过消息本身的返回值传给消息发送方。
所实现的功能与回调函数并无不同。
当然,此例中改为消息纯属画蛇添脚,反倒把程序搞得很慢。
但其他情况下并非总是如此,特别是需要异步调用时,发送消息是一种不错的选择。
假如回调函数中包含文件处理之类的低速处理,调用方等不得,需要把同步调用改为异步调用,去启动一个单独的线程,然后马上执行后续代码,其余的事让线程慢慢去做。
一个替代办法是借API函数PostMessage发送一个异步消息,然后立即执行后续代码。
这要比自己搞个线程省事许多,而且更安全。
如今我们是活在一个object时代。
只要与编程有关,无论何事都离不开object。
但object并未消除回调,反而把它发扬光大,弄得到处都是,只不过大都以事件(event)的身份出现,镶嵌在某个结构之中,显得更正统,更容易被人接受。
应用程序要使用某个构件,总要先弄清构件的属性、方法和事件,然后给构件属性赋值,在适当的时候调用适当的构件方法,还要给事件编写处理例程,以备构件代码来调用。
何谓事件?
它不过是一个指向事件例程的地址,与回调函数地址没什么区别。
不过,此种回调方式比传统回调函数要高明许多。
首先,它把让人不太舒服的回调函数变成一种自然而然的处理例程,使编程者顿觉气顺。
再者,地址是一个危险的东西,用好了可使程序加速,用不好处处是陷阱,程序随时都会崩溃。
现代编程方式总是想法把地址隐藏起来(隐藏比较彻底的如VB和Java),其代价是降低了程序效率。
事件例程(?
)使编程者无需直接操作地址,但并不会使程序减速。
(例程似乎是进程的台湾翻译。
)
三,精妙比喻:
回调函数还真有点像您随身带的BP机:
告诉别人号码,在它有事情时Call您。
回调用于层间协作,上层将本层函数安装在下层,这个函数就是回调,而下层在一定条件下触发回调,例如作为一个驱动,是一个底层,他在收到一个数据时,除了完成本层的处理工作外,还将进行回调,将这个数据交给上层应用层来做进一步处理,这在分层的数据通信中很普遍。
其实回调和API非常接近,他们的共性都是跨层调用的函数。
但区别是API是低层提供给高层的调用,一般这个函数对高层都是已知的;而回调正好相反,他是高层提供给底层的调用,对于低层他是未知的,必须由高层进行安装,这个安装函数其实就是一个低层提供的API,安装后低层不知道这个回调的名字,但它通过一个函数指针来保存这个回调,在需要调用时,只需引用这个函数指针和相关的参数指针。
?
?
?
其实:
回调就是该函数写在高层,低层通过一个函数指针保存这个函数,在某个事件的触发下,低层通过该函数指针调用高层那个函数。
四
软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:
同步调用、回调和异步调用。
同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。
回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。
同步调用是三者当中最简单的,而回调又常常是异步调用的基础。
对于不同类型的语言(如结构化语言和对象语言)、平台(Win32、JDK)或构架(CORBA、DCOM、WebService),客户和服务的交互除了同步方式以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。
对于一般的结构化语言,可以通过回调函数来实现回调。
回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。
在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。
对于象C++或ObjectPascal这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。
Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。
由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。
对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服务、通知服务等。
事件服务和通知服务是CORBA用来处理异步消息的标准服务,他们主要负责消息的处理、派发、维护等工作。
对一些简单的异步处理过程,我们可以通过回调机制来实现。
下面我们集中比较具有代表性的语言(C、ObjectPascal)和架构(CORBA)来分析回调的实现方式、具体作用等。
2过程语言中的回调(C)
2.1函数指针
回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被调函数从而实现回调。
因此,要实现回调,必须首先定义函数指针,请看下面的例子:
voidFunc(char*s);//函数原型
void(*pFunc)(char*);//函数指针
可以看出,函数的定义和函数指针的定义非常类似。
一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。
typedefvoid(*pcb)(char*);
回调函数可以象普通函数一样被程序调用,但是只有它被当作参数传递给被调函数时才能称作回调函数。
被调函数的例子:
voidGetCallBack(pcbcallback)
{
/*dosomething*/
}
用户在调用上面的函数时,需要自己实现一个pcb类型的回调函数:
voidfCallback(char*s)
{
/*dosomething*/
}
然后,就可以直接把fCallback当作一个变量传递给GetCallBack,
GetCallBack(fCallback);
如果赋了不同的值给该参数,那么调用者将调用不同地址的函数。
赋值可以发生在运行时,这样使你能实现动态绑定。
2.2参数传递规则
到目前为止,我们只讨论了函数指针及回调而没有去注意ANSIC/C++的编译器规范。
许多编译器有几种调用规范。
如在VisualC++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。
C++Builder也支持_fastcall调用规范。
调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。
例如:
//被调用函数是以int为参数,以int为返回值
__stdcallintcallee(int);
//调用函数以函数指针为参数
voidcaller(__cdeclint(*ptr)(int));
//在p中企图存储被调用函数地址的非法操作
__cdeclint(*p)(int)=callee;//出错
指针p和callee()的类型不兼容,因为它们有不同的调用规范。
因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列
2.3应用举例
C语言的标准库函数中很多地方就采用了回调函数来让用户定制处理过程。
如常用的快速排序函数、二分搜索函数等。
快速排序函数原型:
voidqsort(void*base,size_tnelem,size_twidth,int(_USERENTRY*fcmp)(constvoid*,constvoid*));
二分搜索函数原型:
void*bsearch(constvoid*key,constvoid*base,size_tnelem,
size_twidth,int(_USERENTRY*fcmp)(constvoid*,constvoid*));
其中fcmp就是一个回调函数的变量。
下面给出一个具体的例子:
#include
#include
intsort_function(constvoid*a,constvoid*b);
intlist[5]={54,21,11,67,22};
intmain(void)
{
intx;
qsort((void*)list,5,sizeof(list[0]),sort_function);
for(x=0;x<5;x++)
printf("%in",list[x]);
return0;
}
intsort_function(constvoid*a,constvoid*b)
{
return*(int*)a-*(int*)b;
}
2.4面向对象语言中的回调(Delphi)
Dephi与C++一样,为了保持与过程语言Pascal的兼容性,它在引入面向对象机制的同时,保留了以前的结构化特性。
因此,对回调的实现,也有两种截然不同的模式,一种是结构化的函数回调模式,一种是面向对象的接口模式。
透析回调函数
“先泛后精,先浅后深”的道理可是知易行难,所以常常会给技术上的小细节纠缠着,搞得晕晕乎,其中“回调函数”就是其中之一了,虽然回用,但是老是不明白其中的含义,众多的书本也说得隐晦不清。
直到不久前看到一篇文章才总算比较清晰了解。
概括起来,回调机制包括两部分:
服务执行者和服务方式制定者。
1.服务执行者先制定服务规范;
2.服务方式制定者然后按照规范制定服务方式;
3.然后执行者按照这个方式提供服务。
回调函数的方式是把函数指针的作为参数传递进去,所以规范就是约定函数的参数类型,个数。
这篇文章如下:
调用(calling)机制从汇编时代起已经大量使用:
准备一段现成的代码,调用者可以随时跳转至此段代码的起始地址,执行完后再返回跳转时的后续地址。
CPU为此准备了现成的调用指令,调用时可以压栈保护现场,调用结束后从堆栈中弹出现场地址,以便自动返回。
借堆栈保护现场真是一项绝妙的发明,它使调用者和被调者可以互不相识,于是才有了后来的函数和构件,使吾辈编程者如此轻松愉快。
若评选对人类影响最大之发明,在火与车轮之后,笔者当推压栈调用。
话虽这样说,此调用机制并非完美。
回调函数就是一例。
函数之类本是为调用者准备的美餐,其烹制者应对食客了如指掌,但实情并非如此。
例如,写一个快速排序函数供他人调用,其中必包含比较大小。
麻烦来了:
此时并不知要比较的是何类数据--整数、浮点数、字符串?
于是只好为每类数据制作一个不同的排序函数。
更通行的办法是在函数参数中列一个回调函数地址,并通知调用者:
君需自己准备一个比较函数,其中包含两个指针类参数,函数要比较此二指针所指数据之大小,并由函数返回值说明比较结果。
排序函数借此调用者提供的函数来比较大小,借指针传递参数,可以全然不管所比较的数据类型。
被调用者回头调用调用者的函数(够咬嘴的),故称其为回调(callback)。
回调函数使程序结构乱了许多。
WindowsAPI函数集中有不少回调函数,尽管有详尽说明,仍使初学者一头雾水。
恐怕这也是无奈之举。
无论何种事物,能以树形结构单向描述毕竟让人舒服些。
如果某家族中孙辈又是某祖辈的祖辈,恐怕无人能理清其中的头绪。
但数据处理之复杂往往需要构成网状结构,非简单的客户/服务器关系能穷尽。
Windows系统还包含着另一种更为广泛的回调机制,即消息机制。
消息本是Windows的基本控制手段,乍看与函数调用无关,其实是一种变相的函数调用。
发送消息的目的是通知收方运行一段预先准备好的代码,相当于调用一个函数。
消息所附带的WParam和LParam相当于函数的参数,只不过比普通参数更通用一些。
应用程序可以主动发送消息,更多情况下是坐等Windows发送消息。
一旦消息进入所属消息队列,便检感兴趣的那些,跳转去执行相应的消息处理代码。
操作系统本是为应用程序服务,由应用程序来调用。
而应用程序一旦启动,却要反过来等待操作系统的调用。
这分明也是一种回调,或者说是一种广义回调。
其实,应用程序之间也可以形成这种回调。
假如进程B收到进程A发来的消息,启动了一段代码,其中又向进程A发送消息,这就形成了回调。
这种回调比较隐蔽,弄不好会搞成递归调用,若缺少终止条件,将会循环不已,直至把程序搞垮。
若是故意编写成此递归调用,并设好终止条件,倒是很有意思。
但这种程序结构太隐蔽,除非十分必要,还是不用为好。
利用消息也可以构成狭义回调。
上面所举排序函数一例,可以把回调函数地址换成窗口handle。
如此,当需要比较数据大小时,不是去调用回调函数,而是借API函数SendMessage向指定窗口发送消息。
收到消息方负责比较数据大小,把比较结果通过消息本身的返回值传给消息发送方。
所实现的功能与回调函数并无不同。
当然,此例中改为消息纯属画蛇添脚,反倒把程序搞得很慢。
但其他情况下并非总是如此,特别是需要异步调用时,发送消息是一种不错的选择。
假如回调函数中包含文件处理之类的低速处理,调用方等不得,需要把同步调用改为异步调用,去启动一个单独的线程,然后马上执行后续代码,其余的事让线程慢慢去做。
一个替代办法是借API函数PostMessage发送一个异步消息,然后立即执行后续代码。
这要比自己搞个线程省事许多,而且更安全。
如今我们是活在一个object时代。
只要与编程有关,无论何事都离不开object。
但object并未消除回调,反而把它发扬光大,弄得到处都是,只不过大都以事件(event)的身份出现,镶嵌在某个结构之中,显得更正统,更容易被人接受。
应用程序要使用某个构件,总要先弄清构件的属性、方法和事件,然后给构件属性赋值,在适当的时候调用适当的构件方法,还要给事件编写处理例程,以备构件代码来调用。
何谓事件?
它不过是一个指向事件例程的地址,与回调函数地址没什么区别。
不过,此种回调方式比传统回调函数要高明许多。
首先,它把让人不太舒服的回调函数变成一种自然而然的处理例程,使编程者顿觉气顺。
再者,地址是一个危险的东西,用好了可使程序加速,用不好处处是陷阱,程序随时都会崩溃。
现代编程方式总是想法把地址隐藏起来(隐藏比较彻底的如VB和Java),其代价是降低了程序效率。
事件例程使编程者无需直接操作地址,但并不会使程序减速。
更妙的是,此一改变,本是有损程序结构之奇技怪巧变成一种崭新设计理念,不仅免去被人抨击,而且逼得吾等凡人净手更衣,细细研读,仰慕至今。
只是偶然静心思虑,发觉不过一瓶旧酒而已,故引得此番议论,让诸君见笑了。
事件驱动程序设计是围绕着消息基础形成的,发生一个事件,伴随着一大堆的消息。
我理解“回调机制”是window在执行某个API函数的过程中,调用指定的一个函数。
我们可以模拟一下:
假设ms提供一个函数叫做 EnumFont,该函数是得到所有的字体,假设它的实现是
EnumFont()
{
while((f=FindNextFont())!
=NULL)
{
printf("fontname:
"+f.name);
}
}
这样就循环显示出所有的字体名称。
但是,开发者可能对字体信息另有用处,那么如何才能让开发者能使用这些信息呢,于是做改进:
EnumFont(void* userFunc)
{
while((f=FindNextFont())!
=NULL)
{
printf("fontname:
"+f.name);
if(userFunc!
=NULL) userFunc(f);
}
}
假设userFunc是一个函数voidf(FontObjectfont).这样使用者只需要定义一个函数:
voidmyfunc(FontObjectfont)
{
listCtrl.Addstring(font.name);
}
通过使用EnumFont(myfunc)就可以将所有额字体信息添加到一个列表框中。
那么我们称myfunc是一个回调函数,即让某个系统函数调用的函数。
因此可以得出结论:
1回调函数是由开发者按照一定的原型进行定义的函数
2回调函数并不由发者直接调用执行
3回调函数通常作为参数传递给系统API,由该API来调用。
4回调函数可能被系统API调用一次,也可能被循环调用多次。
比如函数intEnumFontFamilies(
HDChdc, //handletodevicecontrol
LPCTSTRlpszFamily, //pointertofamily-namestring
FONTENUMPROClpEnumFontFamProc,
//pointertocallbackfunction
LPARAMlParam //pointertoapplication-supplieddata
);
其中的 FONTENUMPROClpEnumFontFamProc就是一个回调函数,该函数遵照格式
intCALLBACKEnumFontFamProc(ENUMLOGFONTFAR*lpelf, NEWTEXTMETRICFAR*lpntm,intFontType, LPARAMlParam)进行定义。
如同mutant所说,回调函数主要用于一些比较费时的操作,或响应不知道何时将会发生的事件,回调函数提供了一种异步的机制,相对于同步执行,提高了效率.前者的例子如WriteFileEx,ReadFileEx等,函数的最后一个参数是一个回调函数的指针,程序中调用WriteFileEx以后,就直接返回了,可以继续进行其他工作,系统在读写操作完成后通知程序作善后处理.后者的例子就是windows的事件机制回调函数的另一个用途,是用于一些枚举函数,如EnumDisplayModes等,每找到一种支持的显示模式,就通知回调函数,由回调函数具体处理,这是因为EnumDisplayModes本身并不知道用户要如何处理.能,用户提供回调函数,定制系统的功能,这样,不同的用户提供不同的回调函数,可以使系统具有不同的功能.这就是所谓的plugin.使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 关于 调函
![提示](https://static.bdocx.com/images/bang_tan.gif)