惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

罗磊的独立博客
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
MyScale Blog
MyScale Blog
M
MIT News - Artificial intelligence
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
The Blog of Author Tim Ferriss
Martin Fowler
Martin Fowler
博客园 - 【当耐特】
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
宝玉的分享
宝玉的分享
Engineering at Meta
Engineering at Meta
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
C
Check Point Blog
Last Week in AI
Last Week in AI
F
Fortinet All Blogs
博客园 - 聂微东
Blog — PlanetScale
Blog — PlanetScale
H
Help Net Security
GbyAI
GbyAI
云风的 BLOG
云风的 BLOG

博客园 - 木子一叶

ffmpeg音视频文件批量转换脚本 2010年下半年软考信息系统项目管理师论文预测-备忘 H.264编解码标准的核心技术(提供相关流程图) 《卓有成效的管理者》书摘(一) 年轻男人必读 一定要知道的71个做饭技巧 c、c++,MFC在笔试中容易出现的内容 VC++6.0中BMP位图的读取和显示 凸多边形最优三角剖分 矩阵连乘最佳加括号方式-动态规划算法 用Visual C++6.0实现棋盘覆盖分治算法 在线查询大全 SPS技术体系之三------Sharepoint2003对象模型 SPS技术体系之二-----数据库表结构分析 SPS技术体系之一-----程序文件和IIS配置 英语学习网站收藏----來自http://yhb20fly.spaces.msn.com/ 每个开发人员应该下载的十种必备工具 转载--完整的UBB代码(C#版) SQL查询优化
回调函数的那些事儿
木子一叶 · 2010-03-02 · via 博客园 - 木子一叶

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

1、基础知识

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

 

①约定接口规范。在模块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);