什么是回调函数

来源:互联网 发布:js动态添加class样式 编辑:程序博客网 时间:2024/06/01 10:06
1、基础知识


所谓回调,就是模块A要通过模块B的某个函数b()完成一定的功能,但是函数b()自己无法实现全部功能,需要反过头来调用模块A中的某个函数a()来完成,这个a()就是回调函数。其实就是把两个函数组合起来,在函数中又调用函数。如下图




例如有两个类class a和class b


class a{


int aa1(int,int);


int aa2(int,int);




}




class b{


int bb1(int,int);


int bb2(int,int);




}




a a1;




b b1;


对象a1中调用b1中的bb2方法,bb2方法不能完全单独完成一个任务,故又在bb2内再调用aa1,被bb2方法调用的aa1就是回调函数。






①约定接口规范。在模块B必须约定接口规范,也就是定义回调函数a()的函数原型


这里回调函数原型的定义最好遵循typedef void (*SCT_XXX)(LPVOID lp, const CBParamStruct& cbNode); SCT_XXX是回调函数名称,lp是回调上下文,CBParamStruct是回调参数,一般由于要回调的参数不止一个,所以定义一个结构体比较方便。


②回调函数的注册。为了让模块B知道自己将要使用的回调函数,必须有一个函数或语句来注册回调函数


注册回调函数的定义遵循void RCF_XXX(SCT_XXX pfn, LPVOID lp); RCF_XXX是注册函数名,pfn是回调函数名称(是指针),lp是回调上下文。一般在A模块初始化完B模块后调用,将A模块中定义的回调函数地址赋值给pfn,lp赋值为this。 


③在模块A中要做的事情:


首先将回调函数声明成静态的,static void  CF_XXX(LPVOID lp, const CBParamStruct& cbNode); 函数的参数必须与B模块中回调函数原型的参数保持一致。


初始化B模块时,调用注册函数将模块A中声明的回调函数CF_XXX的地址传给pfn,即pfn=CF_XXX;(函数名称CF_XXX其实是个指针,指向回调函数的地址) 。


 2、举例


回调函数使用第一个场景:MFC界面编程。有这样一个需求,主界面左侧是一个树形列表,右侧是一个绘图区用来展示左侧列表项的内容,双击绘图区弹出框用来编辑。一般的做法是在绘图区对话框初始化时将主对话框或者树形列表的指针传进来,在绘图区对话框中处理双击事件,在事件出来函数中调用主对话框或树形列表的指针完成更新操作。这样主对话框类和绘图区对话框类之间就出现了互相包含的关系,回调函数这个时候就可以大显身手了,主对话框仅需要包含绘图区对话框的头文件和声明一个绘图区对话框的对象即可。具体做法是:在绘图区对话框中定义回调函数原型和注册回调的函数,并处理鼠标双击事件,在事件函数中发出回调通知。主对话框中按原型定义回调函数,在回调函数中完成树形列表的更新。


回调函数的第二个应用场景:网络编程。 在网络编程中,为了体现模块化,一般把通讯和数据处理划分开来,即通讯模块负责协议定义、数据收发,而数据处理模块只负责对收发的数据进行解析和打包,假如通讯模块开启了一个线程在持续地接收数据,这个时候问题来了,它通过什么手段把数据交到数据处理模块手中呢?每次收到数据,拿到数据处理模块的指针完成相关操作,这样有犯了两个类指针互相指的错误,也破坏了两个模块的独立性。使用回调函数这些问题都迎刃而解了,下面给出部分伪代码:


  通讯模块
typedef void (*DataReceiveCBFunc)(ReceiveParam & recvParam);  // 回调函数原型定义


// 开始接收,数据处理模块调用,相对于注册回调函数
static BOOL StartReceive(DataReceiveCBFunc pfnData, LPVOID lpContext,……);
// 接收数据的线程,一收到数据就通知回调
static UINT TH_Receive(LPVOID lp);
 


  数据处理模块
// 开始接收数据,开启监听线程,调用上面的StartReceive函数
int StartReceiveInfo(int nListenPort, std::string strLocalIP);
// 数据接收回调函数,被CUdpEx::TH_Receive()回调
static void RecvInfoCallback(ReceiveParam &recvParam);  



0 0
原创粉丝点击