回调函数的那些事儿

实际工做中,对于回调函数一直是我不肯意去触碰的东西,一来因为被不少人搞得神秘兮兮的觉得是很高深难懂的技术,二来在通常状况下经过互相包含类指针也可以解决问题,因此一直就不想去研究这个东东,最近一个项目中被经理逼着使用了回调函数,切切实实体会到了它的好处,代码中类之间的关系不再那么错综复杂了,类A想告诉类B一个事情不再须要在A中定义B的指针了,定义回调函数便可。下面把我理解的回调函数写出来,抛出一块砖,个中滋味要各位看客多实践才能体会出来。编程

一、基础知识网络

所谓回调,就是模块A要经过模块B的某个函数b()完成必定的功能,可是函数b()本身没法实现所有功能,须要反过头来调用模块A中的某个函数a()来完成,这个a()就是回调函数。以下图模块化

 

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

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

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

注册回调函数的定义遵循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实际上是个指针,指向回调函数的地址) 。blog

 二、举例

回调函数使用第一个场景: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);  
相关文章
相关标签/搜索