最近写了两个通讯协议程序,都是电力系统中问答式传输规约,一个是基于TCP传输文件的102规约服务端程序,一个是基于串口采集数据的102规约客户端程序。之前还有别的通讯规约,最近更浓烈的期望能够抽象出这些通讯协议程序的一些通用的部分,以便于新的协议来时,我可以少一些工作量。大致上,我将这些通讯程序分成以下几类:

1、通讯协议类,主要负责按照一定传输协议去组装报文,拆解报文,范围包括全部角色的报文(主站、子站)
2通讯控制类,包括各种通讯控制的子类:TCP、串口等,主要负责建立通讯、关闭通讯、发送报文、接收报文
3、服务端逻辑类,主要是站在服务端的角色处理逻辑,具有12两种类型的成员变量
4、客户端逻辑类,主要是站在客户端的角色处理逻辑,具有12两种类型的成员变量

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);