admin 管理员组文章数量: 1184232
2024年3月19日发(作者:中国传统节日网页素材)
CLLBACK回调函数详解
回调函数
详解
一、回调函数
我们经常在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 发送一个异步
消息,然后立即执行后续代码。这要比自己搞个线程省事许多,而且更安全。
如今我们是活在一个 object 时代。只要与编程有关,无论何事都离不开
object。但 object 并未消除回调,反而把它发扬光大,弄得到处都是,只不
过大都以事件(event)的身份出现,镶嵌在某个结构之中,显得更正统,更容
易被人接受。应用程序要使用某个构件,总要先弄清构件的属性、方法和事件,
然后给构件属性赋值,在适当的时候调用适当的构件方法,还要给事件编写处理
例程,以备构件代码来调用。何谓事件?它不过是一个指向事件例程的地址,与
回调函数地址没什么区别。
不过,此种回调方式比传统回调函数要高明许多。首先,它把让人不太舒服
的回调函数变成一种自然而然的处理例程,使编程者顿觉气顺。再者,地址是一
个危险的东西,用好了可使程序加速,用不好处处是陷阱,程序随时都会崩溃。
现代编程方式总是想法把地址隐藏起来(隐藏比较彻底的如 VB 和 Java),
其代价是降低了程序效率。事件例程(?)使编程者无需直接操作地址,但并不
会使程序减速。
(例程似乎是进程的台湾翻译。)
三、精妙比喻:
回调函数还真有点像您随身带的BP机:告诉别人号码,在它有事情时Call
您。
回调用于层间协作,上层将本层函数安装在下层,这个函数就是回调,而
下层在一定条件下触发回调,例如作为一个驱动,是一个底层,他在收到一个数
据时,除了完成本层的处理工作外,还将进行回调,将这个数据交给上层应用层
来做进一步处理,这在分层的数据通信中很普遍。其实回调和API非常接近,
他们的共性都是跨层调用的函数。但区别是API是低层提供给高层的调用,一
般这个函数对高层都是已知的;而回调正好相反,他是高层提供给底层的调用,
对于低层他是未知的,必须由高层进行安装,这个安装函数其实就是一个低层提
供的API,安装后低层不知道这个回调的名字,但它通过一个函数指针来保存这
个回调,在需要调用时,只需引用这个函数指针和相关的参数指针。 其实:
回调就是该函数写在高层,低层通过一个函数指针保存这个函数,在某个事件的
触发下,低层通过该函数指针调用高层那个函数。
四 、无题
软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:
同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执
行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调
用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机
制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,
会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,
通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。同
步调用是三者当中最简单的,而回调又常常是异步调用的基础。
对于不同类型的语言(如结构化语言和对象语言)、平台(Win32、JDK)
或构架(CORBA、DCOM、WebService),客户和服务的交互除了同步方式
以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况
下能够主动通知客户,而回调是实现异步的一个最简捷的途径。
对于一般的结构化语言,可以通过回调函数来实现回调。回调函数也是一个
函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。
在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这
种接口的类成为回调类,回调类的对象成为回调对象。对于象C++或Object
Pascal这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特
性,也能兼容过程语言的回调函数机制。
Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提
供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。
由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函
数的一个特例。
对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服
务、通知服务等。事件服务和通知服务是CORBA用来处理异步消息的标准服务,
他们主要负责消息的处理、派发、维护等工作。对一些简单的异步处理过程,我
们可以通过回调机制来实现。
下面我们集中比较具有代表性的语言(C、Object Pascal)和架构(CORBA)
来分析回调的实现方式、具体作用等。
五、常见编程语言的callback分析
1 N/A
2 过程语言中的回调(C)
2.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.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,尽管两者有相同的返回值和参数列
2.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
#include
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("%in", list[x]);
return 0;
}
int sort_function( const void *a, const void *b)
{
return *(int*)a-*(int*)b;
}
2.4 面向对象语言中的回调(Delphi)
Dephi与C++一样,为了保持与过程语言Pascal的兼容性,它在引入面向对
象机制的同时,保留了以前的结构化特性。因此,对回调的实现,也有两种截然
不同的模式,一种是结构化的函数回调模式,一种是面向对象的接口模式。
附录博文
简介
对于很多初学者来说,往往觉得回调函数很神秘,很想知道回调函数的工作原理。本文将要
解释什么是回调函数、它们有什么好处、为什么要使用它们等等问题,在开始之前,假设你已经
熟知了函数指针。
什么是回调函数?
什么是回调函数
?
简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为
参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。
为什么要使用回调函数?
为什么要使用回调函数
?
因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存
在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。
如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它
提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库
更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多
种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。
回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到
相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数
指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer() API使
用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的
消息队列。
另一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上所有的顶层窗口,为
每个窗口调用一个程序提供的函数,并传递窗口的处理程序。如果被调用者返回一个值,就继续
进行迭代,否则,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传
递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。
不管怎么说,回调函数是继续自C语言的,因而,在C++中,应只在与C代码建立接口,
或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或
函数符(functor),而不是回调函数。
一个简单的回调函数实现
下面创建了一个的动态链接库,它导出了一个名为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);
这两个函数接受以下参数:
版权声明:本文标题:CALLBACK回调函数详解 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/p/1710856997a576550.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
更多相关文章
Lucky网络唤醒实战指南:5步轻松实现远程设备开关机
在当今万物互联的时代,你是否经常需要远程访问办公室电脑、唤醒家中的NAS设备,或者重启机房的服务器?Lucky的网络唤醒(WOL)功能正是为此而生!本文将带你从零开始,详细解析如何通过简单的5个步骤,轻松实现设备的远程开关机管理。
怎么用命令释放IP地址重新获取IP地址_释放ip重新获取ip命令
你们的关注是博主持续更新的动力,感谢大家支持。在Windows操作系统中,您可以通过命令提示符(CMD)使用以下步骤来释放并重新获取IP地址: 1.以管理员身份打开命令提示符(CMD) : 在W
查看电脑ip地址的几种方法(详细简单)_怎么在终端查看本地ip地址
1.终端查询 win+R输入cmd 命令行输入ipconfig 上面的IPv4就是我电脑的ip地址 2.网络和Internet选项查询 点击菜单-设置 选择网络和internet选项
电脑的IP地址是什么?电脑有且仅有一个IP地址吗?_每台电脑的ip地址是唯一的吗
1、什么是IP地址。 IP地址(Internet Protocol Address)是用于在网络中标识设备的一串数字。当我们说“电脑的IP地址”时,通常指的是该电脑在网络中的唯一标识。然而,这并不意味着一台电脑只能有一个IP地
电脑小知识:电脑怎么查看ip地址?_电脑ip地址在哪看
电脑怎么查看ip地址?众所周知,电脑的ip地址是非常重要的,当我们因为某个原因需要用到电脑的ip地址时,该怎么查看呢?又该怎么才能查看的比较准确?到底电脑的ip地址怎么查看?下面就一起来看看吧! 方法1:在网络设置查看
电脑ip地址怎么看?一看就会!_电脑当前ip地址显示
在计算机网络中,IP地址就像是您的电脑在互联网上的邮寄地址,是数据包找到正确目的地的关键。了解您电脑的IP地址是网络连接中的基础,但对于一些用户来说,这似乎是一项神秘的任务。电脑ip地址怎么看?在本文中,我们将探讨几种轻松的方法,让您
怎么查看自己电脑的IP地址_怎么查看自己电脑ip
现在很多时候,我们都会用到电脑的ip地址,但是有些人不知道怎么查看自己电脑的IP地址,现在就介绍下相关解决步骤 方法1 首先点击Windows图标2 然后在对话框中输
如何查看自己windows电脑的IP地址_windows查看ip地址
许多小伙是不是不知道自己电脑IP不知道如何查询,今天博主安利一波给集美们!主要有两种方法,第一种推荐完全小白,第二种,推荐有电脑基础的(最快的)!一,直接查看 打开设置,在搜索栏中搜索控制面板
华为路由器设置指南
华为是中国一大品牌,在国内也是发展突飞猛进,一起来了解华为路由器的详细设置方法吧。 第一步:设置电脑的IP地址和网关 ADSL的设置是通过浏览器访问其设置页面而实现的。在默认的情况下,ADSL的IP地址为192.168
怎么配置无线路由器
第一步:将各种线插在相关位置。使路由器与电源,网线,电脑相连。 产生的效果: 第二步: 把“ Internet 协议版本4”设置为“自动获取IP地址”。
为什么我们家里的IP都是192.168开头的?_为什么现在私有地址仍为192开头
为什么我们家里的IP都是192.168开头的? IP地址是什么 我们知道,网络通讯的本质就是收发数据包。如果说 收发数据包就跟 收发快递一样。那 IP地址
IP 地址_上网ip10.6.80.108是什么网络
IP地址IP地址(Internet Protocol Address)是指互联网协议地址,它是一个32位的标识符,用来唯一标识网络中的计算机。 IP地址通常由4个数字组成,每个数字用“.”分隔。 例如,1
IP地址和 MAC地址详解_ip地址与mac地址
目录 1,什么是IP地址1.查看本机的 IP 和 MAC 地址 MAC地址(称为物理地址),是硬件设备(计算机手机)等唯一标识。 MAC地址对应于OSI参考模型的第二层数据链路层,交换机维
IP地址分类和特殊IP地址_ip地址特殊地址
1.IP地址分类(A类 B类 C类 D类 E类) IP地址由四段组成,每个字段是一个字节,8位,最大值是255,, IP地址由两部分组成,即网络地址和主机地址。网络地址表示其属于互联网的哪一个网络,主机地址表示其属于该
全面解析IP地址:概念、分类、计算与子网划分
目录 一.IP地址的概述1.IP地址的概念 IP地址是唯一标识出主机所在的网络及网络中位置的编号。IP地址就像是网络中的“ 电话号码”,每一台电脑、手机或其他网络设备在网络中都
win7系统连接无线时显示有限的访问权限的解决方案_win7无线有限的访问权限
电脑连接无线的时候,需要验证账号和密码的酒店开放网络一直能连通,但是当连接360wifi等第三方的无线共享时,总是显示“有限的访问权限” ,导致网络不通。经过百度相关资料和自己亲自试验,总结出了一套有效的解决方案,步骤如下:
Windows 7下网络打印机安装与共享指南
简介:本文详细介绍了在Windows 7操作系统中安装带有网络服务器功能的打印机的步骤。首先解释了网络服务器打印机的概念,然后指出了检查网络连接和获取打印机IP地址的重要性。接着,文章阐述了如何通过启用自动发现和文件打印共享功能来安装
WiFi手机可以连接,电脑上也能连接,可以微信聊天,但是不能浏览器上网怎么解决?_把手机wifi上的dns输到电脑上可以用吗
问题描述:WiFi手机可以连接,电脑上也能连接,可以微信聊天,但是不能浏览器上网,显示域名解析错误。 背景:小编过了年回来上班后,发现公寓里的无线网在电脑上无法访问互联网,就非常的纳闷。首先我咨询了电信人工客服,那边告诉我可能
【Ubuntu】添加虚拟网卡的三种方式_ubuntu终端新增一块网卡
1. ifconfig添加 使用 ifconfig -a命令查看已有物理网卡,一般得到如下输出: eno1 ...(省略)lo ...ppp0 ...然后向物理
斑马打印机设置成网络打印机步骤_斑马打印机怎么做网络共享
1.正常连接打印机后,下载“斑马机器改IP地址”文件。 2.用记事本打开文件修改要设置的IP地址,网关及子网掩码,如下图所示。 3. 右击打印机驱动,选择打印首选项-工具-发送文件,然后浏览到此ZPL文件,
发表评论