浅谈HAL设计(3)- 回调函数

在设计HAL,或者把代码结构分层的时候我们不可避免会遇到这种情况,一般函数调用顺序总是高级别函数调用低级别函数,比如HAL调用dirver层,或者app调用middleware。但是总有一些情况需要我们在较低级别的函数中调用较高级别的函数,这时候回调函数(callback)就十分有用了。

什么是回调

举一个例子,老板让张三去参加一个培训,培训时间要1000秒:

void start_training(char* name)
{
    delay(1000);
}

老板还想要让张三去给自己买一杯咖啡,时间需要100秒

void buy_coffee(char* name)
{
    delay(100)
}

老板同时执行了两个并行的任务线程:

execute_task_1()
{
    start_training("ZhangSan")
}
execute_task_2()
{
    buy_cooffee("ZhangSan")
}

张三不能去买咖啡,因为他还在参加培训。于是,老板只能一遍一遍发这个命令,或者等到张三结束培训之后再告诉张三。老板很恼火,觉得自己浪费了很多时间。

张三建议老板使用“回调”:

typedef void(*boss_cb)(char* name);
void start_training(char* name, boss_cb callback)
{
    delay(1000);
    callback(name);
}
void buy_coffee(char* name)
{
    delay(100)
}

于是老板只需要一条指令:

execute_task_1()
{
    start_training("ZhangSan", buy_coffee)
}

张三在培训完会直接去执行买咖啡的指令,老板不用一直等着或者反复催促,节省了老板很多时间,老板很满意,给张三加了薪。

张三在培训时候使用的就是回调函数,他不需要知道回调函数的具体内容,只提供这个接口。具体的内容由老板实现,老板让干什么就干什么,可以培训完去买咖啡,可以培训完去开车,也可以培训完去参加会议。

所以,回调函数是对某段代码的引用,该代码作为参数传递给其他代码,该代码允许较级别的软件层调用较级别的层中定义的函数[1]。在软件设计中,回调这种机制允许dirver这种较低层代码中设计某种接口,然后将具体如何实现这个接口功能留给上层的应用程序层。C语言中可以通过传递函数指针实现。

最简单的回调函数只是作为参数传递给另一个函数的函数指针。 在大多数情况下,回调包含三部分:

  • 回调函数
  • 注册回调函数
  • 执行回调函数

下图显示了这三个部分在回调实现中是如何工作的[2]:

image.png

一个简单的例子

在这个例子中有三个文件:

  • callback.c
  • reg_callback.h
  • reg_callback.c
/* callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* callback function definition goes here */
void my_callback(void)
{
    printf("inside my_callback\n");
}
int main(void)
{
    /* initialize function pointer to
    my_callback */
    callback ptr_my_callback=my_callback;                           
    printf("This is a program demonstrating function callback\n");
    /* register our callback function */
    register_callback(ptr_my_callback);                             
    printf("back inside main program\n");
    return 0;
}
/* reg_callback.h */
typedef void (*callback)(void);
void register_callback(callback ptr_reg_callback);
/* reg_callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* registration goes here */
void register_callback(callback ptr_reg_callback)
{
    printf("inside register_callback\n");
    /* calling our callback function my_callback */
    (*ptr_reg_callback)();                                  
}

编译运行后可以得到输出:

This is a program demonstrating function callback
inside register_callback
inside my_callback
back inside main program

正如图片里所展示的关系,较高层的函数将较低层的函数作为普通调用来调用,并且回调机制允许较低层的函数通过指向回调函数的指针来调用较高层的函数。使用回调函数,相当于把两个异步的任务关联了起来。

所以,在C语言中回调函数只不过是将函数指针传递到需要调用回调函数的代码,使用这种方法,我们可以实现一些比如,错误处理,退出前清理内存等很有实用性的功能。

Reference

[1] https://en.wikipedia.org/wiki/Callback_(computer_programming)

[2] https://www.beningo.com/embedded-basics-callback-functions/