回调函数

来源:互联网 发布:干皮精华液 知乎 编辑:程序博客网 时间:2024/09/21 06:36

回调函数应该和设计相关而不是和语言相关。

在分层设计中,高层次的模块会叫低层次的模块做一些事情,通常是通过函数调用。 
从设计上来说,低层次的模块不应该直接调用高层次模块的函数。

所以高层次模块在叫低层模块做事的时候会注册一个回调函数给低层模块,然后低层模块做完了就调用这个函数。表现在C语言上是个函数指针

调用(calling)机制从汇编时代起已经大量使用:准备一段现成的代码,调用者可以随时跳转至此段代码的起始地址, 执行完后再返回跳转时的后续地址。 CPU为此准备了现成的调用指令,调用时可以压栈保护现场,调用结束后从堆栈中弹出现场地址,以便自动返回。借堆栈保护现场真是一项绝妙的发明,它使调用者和被调者可以互不相识,于是才有了后来的函数和构件,使吾辈编程者如此轻松愉快。若评选对人类影响最大之发明,在火与车轮之后,笔者当推压栈调用。 

话虽这样说,此调用机制并非完美。回调函数就是一例。 
  
函数之类本是为调用者准备的美餐,其烹制者应对食客了如指掌,但实情并非如此。 
例如,写一个快速排序函数供他人调用,其中必包含比较大小。麻烦来了: 
此时并不知要比较的是何类数据--整数、浮点数、字符串?于是只好为每类数据制作一个不同的排序函数。 
更通行的办法是在函数参数中列一个回调函数地址,并通知调用者:君需自己准备一个比较函数,其中包含两个指针类参数,函数要比较此二指针所指数据之大小,并由函数返回值说明比较结果。
 

排序函数借此调用者提供的函数来比较大小,借指针传递参数,可以全然不管所比较的数据类型。被调用者回头调用调用者的函数(够咬嘴的),故称其为回调(callback)。 
  

回调函数使程序结构乱了许多。Windows API 函数集中有不少回调函数,尽管有详尽说明,仍使初学者一头雾水。恐怕这也是无奈之举。无论何种事物,能以树形结构单向描述毕竟让人舒服些。如果某家族中孙辈又是某祖辈的祖辈,恐怕无人能理清其中的头绪。但数据处理之复杂往往需要构成网状结构,非简单的客户/服务器关系能穷尽。 

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 是一个函数 void f( FontObject font).这样使用者只需要定义一个函数: 
 

void myfunc( FontObject font){   listCtrl.Addstring ( font.name);}

 

通过使用 EnumFont ( myfunc) 就可以将所有额字体信息添加到一个列表框中。那么我们称 myfunc是一个回调函数, 即让某个系统函数调用的函数。因此可以得出结论: 
   1 回调函数是由开发者按照一定的原型进行定义的函数 
   2 回调函数并不由开发者直接调用执行 
   3 回调函数通常作为参数传递给系统API,由该API来调用。 
   4 回调函数可能被系统API调用一次,也可能被循环调用多次。 
  

比如 函数

int EnumFontFamilies(   HDC hdc,             // handle to device control   LPCTSTR lpszFamily,  // pointer to family-name string   FONTENUMPROC lpEnumFontFamProc, // pointer to callback function   LPARAM lParam        // pointer to application-supplied data   );   

 

其中的   FONTENUMPROC lpEnumFontFamProc就是一个回调函数,该函数遵照格式 

int CALLBACK EnumFontFamProc( ENUMLOGFONT FAR *lpelf,  NEWTEXTMETRIC FAR *lpntm, int FontType,  LPARAM lParam )

 

进行定义。 
如同mutant所说,回调函数主要用于一些比较费时的操作,或响应不知道何时将会发生的事件,回调函数提供了一种异步的机制, 相对于同步执行,提高了效率.前者的例子如WriteFileEx,ReadFileEx等,函数的最后一个参数是一个回调函数的指针,程序中调用WriteFileEx以后,就直接返回了,可以继续进行其他工作,系统在读写操作完成后通知程序作善后处理. 
  

后者的例子就是windows的事件机制回调函数的另一个用途,是用于一些枚举函数,如EnumDisplayModes等, 
每找到一种支持的显示模式,就通知回调函数,由回调函数具体处理,这是因为 EnumDisplayModes本身并不知道用户要如何处理.能,用户提供回调函数,定制系统的功能,这样,不同的用户提供不同的回调函数,可以使系统具有不同的功能.这就是所谓的plugin. 
  

使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。至于如何定义回调函数,跟具体使用的API函数有关,一般在帮助中有说明回调函数的参数和返回值等。  
  

回调函数:   
回调函数是这样一种机制:调用者在初始化一个对象(这里的对象是泛指,包括OOP中的对象、全局函数等)时, 将一些参数传递给对象,同时将一个调用者可以访问的函数地址传递给该对象。这个函数就是调用者和被调用者之间的一种通知约定 ,当约定的事件发生时,被调用者(一般会包含一个工作线程)就会按照回调函数地址调用该函数。    
   

这种方式,调用者在一个线程,被调用者在另一个线程。   
在Windows  API中有一些函数使用回调函数,例如CreateThread、SetWindowLong等。对应的回调函数定义为如下形式:  

function  CallBackFunc(Wnd:  HWND;  Msg,  WParam,  LParam:  Longint): Longint;stdcall;  procedure  ThreadFunction(Ptr:  Pointer);stdcall;  
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 电脑开机密码忘了怎么办xp系统 我的电脑在开机时忘了密码怎么办? xp桌面我的电脑图标不见了怎么办 游戏全屏时卡了无法退到界面怎么办 u盘插电脑上提示有病毒怎么办 三星手机文件怎么删除不掉怎么办 用夜神模拟器玩第五人格太卡怎么办 雷电模拟器玩刺激战场太卡了怎么办 绝地求生刺激战场模拟器太卡怎么办 ddj sb2打碟功能没了怎么办 驼背怎么办 要能快速矫正的方法 苹果7中间的home键坏了怎么办 苹果6p的home键不管用怎么办 华为获取数据失败请检查网络怎么办 三星手机未解锁刷机变砖怎么办 手机显示充电但是充不进去怎么办 手机拔出显示无法连接移动网怎么办 手机上的音乐老是显示网络忙怎么办 华为手机账号换手机忘记密码怎么办 墨墨背单词的注册邮箱忘了怎么办 华为手机华为账号密码忘记了怎么办 手机玩游戏降频特别厉害怎么办 苹果应用商店id登录老卡怎么办 苹果id忘记了自动续费怎么办 红米手机小米账号密码忘了怎么办 小米5splus没系统卡米怎么办 注册谷歌账号输入手机好怎么办 谷歌注册电话号码用了太多次怎么办 内存卡用了深度清理的软件怎么办 华为畅享5s密码忘了怎么办 小米5s进水了一直开机关机怎么办 手机菜单键功能键返回键失灵怎么办 小米5s更新系统发热严重怎么办 小米手机4G网速不好怎么办力 红米5 plus开不开机怎么办 小米3s手机触屏部分失灵怎么办 魅族手机屏幕锁密码忘了怎么办 手机没设置魅族账号密码忘了怎么办 魅族手机格式化密码忘了怎么办 魅族手机忘记密码了怎么解锁怎么办 手机设置的应用加密忘记密码怎么办