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);

这两个函数接受以下参数:

本文标签: 函数 回调 调用 消息 地址

更多相关文章

Lucky网络唤醒实战指南:5步轻松实现远程设备开关机

1月前

在当今万物互联的时代,你是否经常需要远程访问办公室电脑、唤醒家中的NAS设备,或者重启机房的服务器?Lucky的网络唤醒(WOL)功能正是为此而生!本文将带你从零开始,详细解析如何通过简单的5个步骤,轻松实现设备的远程开关机管理。

透明窗体 使用DWM实现Aero Glass效果_当dwm禁用时,透明窗口将失效

1月前

转多转 ----------------------------------------- 从Windows Vista开始,Aero Glass效果被应用在了Home Premium以上的系统中(Home Basic

怎么用命令释放IP地址重新获取IP地址_释放ip重新获取ip命令

1月前

你们的关注是博主持续更新的动力,感谢大家支持。在Windows操作系统中,您可以通过命令提示符(CMD)使用以下步骤来释放并重新获取IP地址: 1.以管理员身份打开命令提示符(CMD) : 在W

如何释放并重新获得ip地址呢?_ip释放 ip重新获取

1月前

如何释放并重新获得ip地址呢? 释放并重新获得一个IP地址的具体步骤如下:1、要想从DHCP服务器重新获取ip,电脑必须设置成"自动获取ip",设置如下,在电脑桌面"网络"-属性-

如何快速更改电脑IP地址_windows命令行更改ip地址

1月前

一、如何快速更改电脑IP地址 1.1 打开命令提示符 以管理员身份运行命令提示符(Command Prompt)按 Win + X,选择“命令提示符(管理员)”或“Windows PowerShell(管理

从Windows到手机:一步步教你查询IP地址_手机, 命令行ip查询

1月前

IP地址是互联网中用于唯一标识设备的数字标签,它帮助区分不同的设备并实现网络通信。在设置网络、进行远程协助或排查网络问题时,我们可能需要知道自己的IP地址。然而,许多人并不清楚如何查询本机的IP地址。本文将介绍IP地址的基础知识以及在

电脑ip地址怎么设置_设备管理器改ip

1月前

在互联网时代,IP地址是每台电脑在互联网中的唯一标识。合理设置电脑的IP地址,可以更好地管理网络连接和资源。电脑IP地址怎么设置?本文将简单给大家分享几种设置方式。有需要的用户可以继续往下看啦!方法一:手动设置

如何查看电脑的IP地址_410144cm查询ip地址的方法

1月前

要查看电脑的IP地址,有几种常用的方法,最常见的是通过命令提示符或网络设置来查看。 方法一:使用命令提示符(Windows) 1.打开命令提示符:按下Win + R键,输入cmd并点击确定,打开

IP地址:由电脑还是网线决定?_ip地址和电脑有关系还是和网络有关系

1月前

IP地址:由电脑还是网线决定? 在互联网时代,IP地址是我们进行网络通信的基础。然而,对于IP地址究竟是由电脑决定还是由网线决定的问题,不少人可能存在疑惑。本文将从IP地址的定义、分配方式以及影响因素等方面进行探讨,旨在帮助读

TENDA 路由器产品常见问题解答_tenda路由器ftp软件连不上

1月前

TENDA 路由器产品常见问题解答 TENDA 路由器产品常见问题解答 为什么我无法登录至宽带路由器设置页面?首先确认路由器与电脑已经正确连接(请参考怎样确认我的电脑与路由器已经正确连接),

怎么配置无线路由器

1月前

第一步:将各种线插在相关位置。使路由器与电源,网线,电脑相连。 产生的效果: 第二步: 把“ Internet 协议版本4”设置为“自动获取IP地址”。

为什么IP地址一般是192.168开头

1月前

先总结 IP地址以192.168开头的是私有IP地址,这些地址被保留用于局域网(LAN)内部使用。私有IP地址不会在互联网上路由,因此它们可以被多个不同的局域网重复使用。这种设计有助于节省公共IP地址资源,并且增加了网络的安全

IP与MAC地址解析

1月前

查看本机的 IP 和 MAC 地址 在cmd输入以下命令 C:Users35211> ipconfigallMAC地址 MAC地址(称为物理地址),是硬件设备(计算机手机)等唯一标识。MAC

【IP地址】_ip地址示例

1月前

IP地址互联网的每一个节点都会有一个IP地址IP地址的分类版本IPv4地址、IPv6地址IPv4地址–32位的二进制数,由点分十进制的书写方法表示二进制与十进制之

IP地址和子网划分学习笔记之《IP地址详解》_通常我们把ip地址中间的点

1月前

在学习IP地址和子网划分前,必须对进制计数有一定了解,尤其是二进制和十进制之间的相互转换,对于我们掌握IP地址和子网的划分非常有帮助,可参看如下目录详文。 IP地址和子网划分学习笔记相关篇章: 一、IP地址和M

怎样安装带网络接口的打印机_打印机带网络接口

1月前

这篇文章介绍了安装打印机的步骤。本文讲述的是在计算机上安装带网络端口的打印机,即使用网络端口来进行通讯(还可以用串口、及并口等)。首先要连接打印机至计算机所在的局域网内,并在打印机的面板上设置打印机的IP地址为和计算机同一网段内的IP

Linux如何查找域名IP地址_linux 获取域名ip

1月前

这篇文章主要介绍了在Linux终端中如何查找域名IP地址,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。 可以使用以下 5 个命令来完成此操作。d

Linux 中查找 IP 地址的方法_linux查看ip地址

1月前

概要 在 Linux 系统中,经常需要查找 IP 地址以进行网络配置、故障排除或安全管理。无论是查找本地主机的 IP 地址还是查找其他设备的 IP 地址,本文将介绍三种简单的方法,帮助你在 Linux 中轻松找到所需的 IP

WiFi手机可以连接,电脑上也能连接,可以微信聊天,但是不能浏览器上网怎么解决?_把手机wifi上的dns输到电脑上可以用吗

1月前

问题描述:WiFi手机可以连接,电脑上也能连接,可以微信聊天,但是不能浏览器上网,显示域名解析错误。 背景:小编过了年回来上班后,发现公寓里的无线网在电脑上无法访问互联网,就非常的纳闷。首先我咨询了电信人工客服,那边告诉我可能

CSDN热议:解开IP地址与子网划分的神秘面纱

1月前

目录一、IP地址 1.1.IPv4 IPv4由32位二进制数组成,一般用点分十进制来表示IPv4是由32位二进制数组成,分成四组,每组八位。例如:11000000.10101000.00000

发表评论

全部评论 0
暂无评论