最近需要实现处理AWSIOT传来的消息回调函数。作为库编程,在老司机的指导下发现不能直接把AWS IOT的回调接口暴露到上层而是应该自己封装回调函数以供上层调用,这样可以更好地解耦合,上层即不需要了解下层的细节。这里就发现了原来对回调函数的使用还存在一定的误区,这里特地整理一篇文章以供查阅。实质上就是传入一个函数指针 内部去调用。 可以参考Linux内核callback调用方式。
第一节主要展示什么是回调函数
第二节主要讨论回调函数的常用用法
1 回调函数基本概念及源码
1.1 回调函数概念
个人理解回调函数就是:
在一个函数中调用另一个通过参数传入的函数,而这个传入的函数就是回调函数。
用伪代码标识如下:
functionB(){
printf("i am callback");
}
functionA(paramA){
printf("I will call the callback next");
paramA();
}
functionA(functionB);
可以看到调用functionA时传入functionB, 而在functionA中 调用functionB,所以functionB被称为回调函数。
而在C语言的实现可以借用百度百科的定义:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
1.2 sample
这里就提供一份sample code 进行说明回调函数形式:
#include <stdio.h>
// 架设libPrintfFunction 函数即为 系统编程人员 提供的接口,这个函数会调用应用开发者提供的回调函数
typedef int (*libCallbackFunction_t)(char* str); //定义回调函数格式 为传入为char* return 为int的函数指针
int libPrintfFunction(char *str, libCallbackFunction_t callback){//假设我是lib开发者(系统编程),在提供接口时,想给应用开发者(应用编程)执行它想要执行自定操作操作 提高灵活性
printf("This is lib function,printf main str: %s\n", str);
char* libstr = "this is lib str";
callback(libstr);
return 0;
}
int mainCallbackPrintf(char *str){
printf("This is main function,printf lib str: %s\n", str);
return 0;
}
int main(){
char* mainStr = "this is main str";
libPrintfFunction(mainStr, mainCallbackPrintf);
return 0;
}
编译
gcc callback.c -o test
这份代码功能即为 主函数会调用lib开发人员提供的libPrintfFunction, 并传入约定好的回调函数得到lib函数中传入值。 这里我们在回调函数中打印出lib传入值。这里就可以很清楚地看到mian函数调用了libPrintfFunction,并调用时传入了自己定义的mainCallbackPrintf 这个函数。而这个函数在libPrintfFunction 中被调用,所以mainCallbackPrintf 就是回调函数。
1.3 封装成lib的sample
可能这样通常大家看到的回调函数不太一样,我们将这个函数封装成lib,可能就会更加熟悉了。
现在将上面这个函数拆成三个部分,并把lib函数分装成库函数。将库函数单独定义成一个.c和对应.h
lib.c:
#include <stdio.h>
#include "lib.h"
int libPrintfFunction(char *str, libCallbackFunction_t callback){//假设我是lib开发者(系统编程),在提供接口时,想给应用开发者(应用编程)执行它想要执行自定操作操作 提高灵活性
printf("This is lib function,printf main str: %s\n", str);
char* libstr = "this is lib str";
callback(libstr);
return 0;
}
lib.h:
#ifndef _LIB_H_
#define _LIB_H_
typedef int (*libCallbackFunction_t)(char* str); //定义回调函数格式 为传入为char* return 为int的函数指针
int libPrintfFunction(char *str, libCallbackFunction_t callback);
#endif
callback.c
#include <stdio.h>
#include "lib.h"
int mainCallbackPrintf(char *str){
printf("This is main function,printf lib str: %s\n", str);
return 0;
}
int main(){
char* mainStr = "this is main str";
libPrintfFunction(mainStr, mainCallbackPrintf);
return 0;
}
这里把lib.c 打包成静态库
gcc -c lib.c -o lib.o
ar rcs -o lib.a lib.o
这样就得到了静态lib.a 和lib.h
使用lib.a和我们主函数编译可支持文件
gcc callback.c lib.a -o test
测试
This is lib function,printf main str: this is main str
This is main function,printf lib str: this is lib str
这里就是通常我们用到的回调函数的场景了
通过我们使用第三方lib时,会提供一个.a 和,h
#ifndef _LIB_H_
#define _LIB_H_
typedef int (*libCallbackFunction_t)(char* str); //定义回调函数格式 为传入为char* return 为int的函数指针
int libPrintfFunction(char *str, libCallbackFunction_t callback);
#endif
我们在.h中就可以看到 libPrintfFunction 这个方法提供了一个回调 函数的接口。
而回调函数的形式是传入char* 返回int的函数。所以就在主函数构造一个这个形式的函数传入libPrintfFunction,当
libPrintfFunction执行到一定逻辑时,就会触发我们传入的回调函数。
2 回调函数使用场景
上面已经介绍什么是回调函数,这里描述一下我理解回调函数的应用场景。
2.1 库函数开发
举一个例子:
比如现在需要开发一个lib 对上层提供调动,基础的功能是lib 中固定对传入数据执行一些处理(比如打印传入),然后执行一段算法,又进行一些处理(比如打印算法处理后的结果)。这里我们可以大概描述出这段代码
我们假设这个算法是对传入值乘100:
int libfunction(int a){
printf("%d\n", a);
a = a * 100;
printf("%d\n", a);
}
上面的函数就是你要编写的lib的实现。看起来还不错 也没有回调函数什么事。
但这里实质上是lib提供者实现的,如果产品应用中经常变更这种算法(md 需求又变了),那么意味着lib提供者也要更新lib。这种情况下lib开发者就会觉得特别烦,自然而然可以可以对外提供一个回调函数,把算法实现交给上层应用程序员去实现,每次算法改动就不会反复需要lib提供者更新,这样编写的lib才更加灵活。
int libfunction(int a, int (* callback)(int *)){
printf("%d\n", a);
a = callback(a);
printf("%d\n", a);
}
而main函数中回调函数就可以写成这样了
int callback(int a){
a = a * 100;
return a;
}
libfunction(10, callback);
这样提供的代码的灵活性。当然上面这种回调是阻塞的。
2.2 基于事件驱动编程
事件驱动是指在持续事务管理过程中,进行决策的一种策略,即跟随当前时间点上出现的事件,调动可用资源,执行相关任务,使不断出现的问题得以解决,防止事务堆积。所谓事件驱动,简单地说就是你点什么按钮(即产生什么事件),电脑执行什么操作(即调用什么函数).当然事件不仅限于用户的操作. 事件驱动的核心自然是事件。
很简单应用场景就是,货到了通知我。
货到了就是事件,而通知我就是事件对应的处理操作。
而在实际使用中,我们就调用一个监听函数,可以相应的回调函数(处理函数)。
来段伪代码:
int callback(int a){
printf("I will buy Thing");
}
eventListen('ThingArrive', callback);
在我们实际应用中就有很多这种操作,比如与系统硬件交互。
我们调用打开摄像头时,实质上是发起了命令,系统操作硬件打开,这里我们使用事件驱动编程的话,就可以定义一个回调函数执行摄像头打开成功后的操作,并把回调函数注册到监听摄像头打开成功这个事件上。
在网络上通信中就更加常用这种回调地处理方式。
上诉回调操作就可能都为非阻塞的。
实质上我们想要实现这种非阻塞回调函数,可以在传入回调函数的函数中开辟一个线程即可。这样主线程就可以实现非阻塞的效果。而子线程触发相应事件后就会调用回调函数。
3 源码
c_callback.zip
链接: https://pan.baidu.com/s/1Hd0I8B82h9An_pm8fQ60xQ 提取码: 4mpt
参考链接
https://blog.csdn.net/morixinguan/article/details/65494239
https://www.zhihu.com/question/19801131