最近写了两个通讯协议程序,都是电力系统中问答式传输规约,一个是基于TCP传输文件的102规约服务端程序,一个是基于串口采集数据的102规约客户端程序。之前还有别的通讯规约,最近更浓烈的期望能够抽象出这些通讯协议程序的一些通用的部分,以便于新的协议来时,我可以少一些工作量。大致上,我将这些通讯程序分成以下几类:
1、通讯协议类,主要负责按照一定传输协议去组装报文,拆解报文,范围包括全部角色的报文(主站、子站)
2、通讯控制类,包括各种通讯控制的子类:TCP、串口等,主要负责建立通讯、关闭通讯、发送报文、接收报文
3、服务端逻辑类,主要是站在服务端的角色处理逻辑,具有1、2两种类型的成员变量
4、客户端逻辑类,主要是站在客户端的角色处理逻辑,具有1、2两种类型的成员变量
5、界面类,负责与用户、逻辑类交互
听起来是很美,做到逻辑类的时候也还算顺畅,到了做界面类与逻辑类的交互时就纠结啦~我是有强迫症的,如果结构太累赘就心塞~之前写别的协议时,因为刚接触也不太了解业务,时间又紧,只好忍着把逻辑类和界面类揉在一起,这次说什么也要把他们分开!不幸的是,界面类可以方便的输出日志到界面,却不可以直接发送和接收报文,而逻辑类恰恰相反,虽然把控着报文,对于输出报文到界面却是短腿。。。而我又不希望这两个类互相包含头文件。。。好吧,强迫症就是作==||。。。
经过小师傅的提点,我用回调函数解决了这个问题。大致如下:
首先在界面类中添加一个静态的回调函数:
static void CALLBACK PrintLogCallback(void* pInstance, const char* str_log, int len);
千万别小看第一个参数,他可是可以动用界面类中的非静态兵力的制胜法宝啊!实现此函数的时候可以这样写:
void CCOM102Dlg::PrintLogCallback(void* pInstance, const char* str_log, int len) { CCOM102Dlg* pCOM102Dlg = (CCOM102Dlg*) pInstance; //然后拿着pCOM102Dlg 这个兵符,你想干啥干啥去吧 //比如你可以像下面这样调用自定义的打印日志方法: //pCOM102Dlg->AppendLog(str_log, len); }
这样完成第一步,第二步就是要在逻辑类中添加调用回调函数的接口,如下:
typedef void (CALLBACK *PrintLogCallback)(void* pInstance, const char* str_log, int len); //设置与界面类交互需要的回调函数,保存函数指针 void SetInterfaceEngine(void *pInstance, PrintLogCallback printLog); //保存与回调函数有关信息的结构体 struct InterfaceEngine { void *pInstance; PrintLogCallback printLog; }; InterfaceEngine m_engine; //逻辑类的打印日志函数 void PrintLog(const char* buf, int len);
然后在需要用到界面类的打印日志功能时,找你保存的函数指针替你办事就可以啦!
void CClassLogic::SetInterfaceEngine(void *pInstance, PrintLogCallback printLog) { m_engine.pInstance= pInstance; m_engine.printLog = printLog; }
void CClassLogic::PrintLog(const char* buf, int len) { if (m_engine.pInstance!= NULL) { m_engine.printLog(m_engine.pInstance, buf, len); } }
所以现在只需要在界面类包含逻辑类的头文件就可以了~声明一个CClassLogic的成员变量m_cLogic,然后调用设置回调函数的接口就可以看到效果了:
m_cLogic.SetInterfaceEngine(this, COM102Dlg::PrintLogCallback);