本文向大家介绍了什么时函数型指针和回调函数,并提供了本人实现并亲测OK的一种通用的回调函数注册方法的源码,适合做底层开发的同学借鉴。
什么是回调函数
回调函数一般在操作系统里用的比较多。另外在多人协同开发的大型项目里,做底层库的开发者通常会将底层事件的处理以注册回调函数的形式给到应用层,这样应用层可根据业务需求随意处理,底层库摆脱了和应用层的耦合,也可以很方便的移植到其他应用上。操作系统之所以使用回调,也是这一理由。
底层向应用层提供回调函数注册接口,用户通过向系统的某一事件注册回调函数,完成对该系统事件的处理。
用户只需传递回调函数的入口地址,该系统事件发生时,就会自动调用回调函数处理相关事件。
什么是函数型指针
函数型指针的概念和定义
就和数组型指针是指向某一固定类型的数组一样,函数型指针,是指向某一固定类型的函数的指针。C语言中一般通过下面的方式来定义该指针的类型(是一种自定义的数据类型, 就是结构体是一种自定义类型一样):
typedef int (* callback_t)(void * arg);
意思就是,自定义了一个叫做callback_t
的数据类型,是一种指针数据类型,它指向所有参数为void * arg
返回值为int
型的函数。
我用callback_t
这个数据类型来定义一个指向上述函数类型的指针:
callback_t fnc_cb;
再定义一个对应类型的函数:
int handler_cb( void * arg )
{
if(arg)
{
printf("test string: %s", (char*)arg);
return 0;
}
return -1;
}
有一点大家要明白:函数名就是一个指针常量,指向该函数的入口地址, 就像数组名是指向数组首地址的指针常量一样。
明白了这一点后,我们就可以用handler_cb
这一指针常量给fnc_cb
这一指针型变量赋值:
fnc_cb = handler_cb;
这样,我们就完成了一个函数型指针的定义和赋值。
如何通过函数型指针来引用函数
我们上面完成了函数型指针的定义和赋值,通过下面的这种形式即可完成对handler_cb
的调用。
fnc_cb("hello world");
执行完该语句后,会输出test string: hello world
这样的字符串。这样就完成了通过函数型指针来引用函数。
其实上述内容已经是最基本的回调函数的用法了。
而下面所说的则是在上面的基础上完的一点花活,提供一种通用的回调函数的注册方法,演示了回调函数如何注册、调用和解除注册:在枚举中列出所有需要回调处理的事件,同一事件可以注册N多次,以满足在不同的程序文件里,对同一事件的不同处理。
比如,对于WiFi连接上路由器这一事件,同一个工程里,在main.c中需要等WiFi连接上路由器以后,才能执行连接服务器的操作,而在device.c中,WiFi连接上路由器后需要播报一段语音提示用户,设备已联网。这两种不同的应用,就要针对同一事件注册两种不同的回调函数进行处理:
假设在main.c中定义了一个名为wifi_handler_connect_server
的函数,用来连接服务器,在device.c中定义一个名为wifi_handler_audio_play
的函数用来播报语音。因为是在不同的.c文件中定义的回调函数,同一事件的回调函数的名称可以定义成相同的,为了避免编译报错,需用static
关键字进行修饰。比如都定义成static int wifi_handler(void * arg)
,代码比较整齐易读。
好了,说了那么多废话,嘴都干了,让我们继续往下看 ……
回调函数的注册、调用和解除注册
为了能够更加方便的理解下面的内容,请大家到github上fork我的源码,对着源码更容易理解,地址如下:
https://github.com/githubChenchi/lib_callback.git
事件枚举
typedef enum {
EVENT_0,
EVENT_1,
EVENT_2,
EVENT_3,
EVENT_4,
EVENT_5,
EVENT_6,
EVENT_7,
EVENT_8,
EVENT_9,
EVENT_MAX /* the max number of the event to be registered */
} event_t;
事件的枚举根据不同的应用自行定义。
回调注册控制块结构
typedef struct {
callback_t cb[REGISTER_NUM_MAX]; /* save the callback functions that have been registered */
unsigned int count; /* save the number of the callback functions that have been registered */
} callback_s;
该结构有两个成员:
指针型数组
cb
cb
的每一个元素都是一个函数型的指针,用于存储某一事件已注册的回调函数名称。计数器count
count
用来存储某一事件已注册的回调函数的次数。
结构中还有一个宏定义REGISTER_NUM_MAX
用来指明一个事件最做多允许被注册多少次,根据自己需要修改,本例为10。
定义好回调注册控制块结构以后,还要定义一个数组:
callback_s callback[EVENT_MAX];
一个回调注册控制块结构对应一个事件,那么该数组就是不同事件的回调注册控制块结构的集合,其数组长度就是所有事件的个数EVENT_MAX
。
注册回调函数的算法
有了上面的准备工作以后,向系统注册回调函数的过程是怎样的呢?
首先贴上源码:
void callback_register( event_t event, callback_t cb_fn )
{
int count = 0;
if(event >= EVENT_0 && event < EVENT_MAX)
{
for(count=0; count<REGISTER_NUM_MAX; count++)
{
if(callback[event].cb[count] == NULL)
{
if(cb_fn)
{
callback[event].cb[callback[event].count++] = cb_fn;
printf("[SUCCESS] event [%d] has been registered [%d] times \r\n", event, callback[event].count);
}
else
{
printf("[WARNING] event [%d] callback function can not be NULL \r\n", event);
}
break;
}
}
if(count == REGISTER_NUM_MAX)
{
printf("[FORBIDDEN] the callback functions register count of this event [%d] has been up to max \r\n", event);
}
}
else
{
printf("[ERROR] illegal event [%d] \r\n", event);
}
}
很简单:
如果传递的第一个参数不在事件类型范围内,提示错误,并退出注册;否则继续往下执行。
通过遍历整个回调注册控制块结构数组
cb
,遇到第一个为NULL
(初始化时设置为NULL
)的元素后,如果第二个参数传递的不是NULL
,就把回调函数名称赋值给该元素,同时计数器加1,停止遍历数组;否则给出警告,提示用户,注册的回调函数不能为NULL
。如果数组遍历完成后,没有任何一个元素为
NULL
,说明以达到最大的注册次数,给出提示,禁止注册。
调用回调函数的算法
void callback_execute( event_t event )
{
int count = 0;
if(event >= EVENT_0 && event < EVENT_MAX)
{
for(count=0; count<REGISTER_NUM_MAX; count++)
{
if(callback[event].cb[count])
{
callback[event].cb[count](NULL);
}
}
}
else
{
printf("[ERROR] illegal event [%d] \r\n", event);
}
}
通过遍历整个回调注册控制块结构数组cb
,所有不为NULL
的元素,即为被注册过的函数,通过引用数组里保存的函数型指针来调用回调函数:callback[event].cb[count](NULL);
解除注册回调函数的算法
void callback_unregister( event_t event, callback_t cb_fn )
{
int count = 0;
if(event >= EVENT_0 && event < EVENT_MAX)
{
for(count=0; count<REGISTER_NUM_MAX; count++)
{
if(callback[event].cb[count] == cb_fn && cb_fn)
{
callback[event].cb[count] = NULL;
callback[event].count--;
printf("[SUCCESS] event [%d] has been registered [%d] times \r\n", event, callback[event].count);
break;
}
else if(cb_fn == NULL)
{
printf("[WARNING] the callback function can not be NULL \r\n");
break;
}
}
if(count == REGISTER_NUM_MAX)
{
printf("[WARNING] the callback function is not registered \r\n");
}
}
else
{
printf("[ERROR] illegal event [%d] \r\n", event);
}
}
与注册回调函数的算法是相逆的过程,不再赘述。