C语言 回调函数原理及实现

最近需要实现处理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

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值