之前在应用层面看过回调函数,什么CallBack()之类的,一直理解为某种中断函数,也只关注函数里面要怎么写逻辑代码,并没有关注回调函数的来龙去脉,现在学习了函数指针后就进一步理解了其来龙去脉。
我现在的理解是回调函数是写在应用层文件中的,回调函数的形参输入是要获取驱动文件中的类型数据,但是从形式上看回调函数的形参输入又不是直接调用驱动文件中的相关类型数据,而是通过调用另一个驱动文件中的函数,把这个回调函数放到这个驱动文件函数中的某个形参中去实现的,所以这里说的回调是指把应用层函数回调给驱动文件,在驱动文件里实现对回调函数的形参输入,然后再通过某一机制,触发这个回调函数,这样就执行了这个回调函数,处理了来自驱动文件中的输入数据具体看一个代码例子。
//----------------------------------------------------------------------------------
应用文件main.c中
#include "drive.h"
int catchdata_work(uint8_t x,uint8_t y) //回调函数
{
int val = x*y+1; // 3*4+1=13
return val;
}
int main()
{
CatchWork_Fun_Reg(catchdata_work);//向驱动层注册一个应用层的回调功能函数
Catchdata_Handle();//通过某个事件触发回调功能函数
while(1)
{
delay(1000);
}
}
//-----------------------------------------------------------------------------------
驱动文件drive.c中
#include "drive.h"
static CATCHDATA catchdata; //声明一个函数指针变量
//应用层函数回调传递,也是应用层函数在驱动层这里进行注册的一个过程
void CatchWork_Fun_Reg(CATCHDATA pcatchdata)
{
catchdata = pcatchdata; //将函数指针变量指向了和其结构类型相同的应用层回调函数的地址
}
static int get_catchdata() //假设这是驱动层对外部某设备数据采集得到的值并进一步处理
{
int16_t get_data = 0x0302; //假想得到的外部设备数据
return (get_data + 2); //返回处理
}
void Catchdata_Handle() //被应用层调用触发
{
uint8_t get_x = get_catchdata(); //get_x = 4
uint8_t get_y = get_catchdata()>>8; // //get_x = 3
(*catchdata)(get_x ,get_y); //使得函数指针指向的应用层函数得到执行
}
//-------------------------------------------------------------------------------------
驱动文件drive.h中
typedef int (*CATCHDATA)(uint8_t x,uint8_t y);//重定义函数指针结构用CATCHDATA这个名字代替
void CatchWork_Fun_Reg(CATCHDATA pcatchdata);//
void Catchdata_Handle();
这个例子中,我们假设main文件中写应用层代码,catchdata_work() 函数要处理外部某个驱动设备的采集数据,但是并不是直接引入外部驱动设备的数据,而是要将catchdata_work() 函数输入到驱动文件的CatchWork_Fun_Reg(被回调的函数)形参中,这个过程就是我们通常说的注册。注册完了以后我们可以看到驱动文件drive.c中,声明的函数指针变量指向了形参,也就是指向了被注册的回调函数 catchdata_work() 的地址
后面就等待外部设备触发Catchdata_Handle() 这个很眼熟,写单片机代码时经常碰到类似的外设中断触发函数handle() ,然后在里面又会有一个callBack() 回调函数,那我们这里这个函数也是由驱动设备驱动文件提供,由应用层来调用,从代码中我们可一看到这个触发函数中对驱动层面上的设备数据进行了处理,调用了内部静态函数get_catchdata(),同时呢也对应用层的回调注册函数进行了执行,但并不是直接调用回调函数,而是通过函数指针(*catchdata)(get_x ,get_y);方式处理,这是个间接调用方式,这样一来,应用层的注册回调函数catchdata_work()就处理了驱动设备采集的数据 uint8_t x uint8_t y ,这里要注意的是定义函数指针的形参结构一定要和被指向的函数形参结构一至。
整体来看,从应用层main.c文件来说,如果这个main.c文件专门是由一个程序员来写,他只要调用那个注册函数CatchWork_Fun_Reg()并把自己需要回调给驱动程序的函数注册到注册函数中去就可以了,然后就是找时机触发驱动文件提供的触发函数来完成回调函数的执行,他并不需要关心驱动程序中的其他细节,很可能驱动程序是另外一个开发者写的只是提供了一个库,只引出函数接口其他什么都看不了。对于驱动开发者来说,他也无需关注应用层开发者写的回调函数的具体内容,他只要创建一个和被回调的应用层函数形参结构相同的函数指针变量,在自己提供的注册函数中指向该被回调函数的地址就行了,后面调用该函数指针变量输入相应的实体形参就能让应用层的回调函数处理自己驱动设备中的数据了。