在熟悉任务调度、 程序分层 和模块化编程关于软件架构、分层和模块设计后,除了函数调用设计中出现的情况外,还会遇到同层模块之前如何进行消息交互,通常是应用层之间。
比如一个设备通过架构设计包含 人机交互应用层模块 (一般会调用按键和显示屏等功能驱动模块)和 通信应用层模块 (一般调用串口、CAN和网络ESP8266等功能驱动模块),两个同层之间的模块如果需要互传数据,一般都是调用各自头文件提供的接口(模块对外提供的接口尽量不要使用全局变量,防止其他模块擅自修改),这样就造成了耦合。
上述情况,也可以采用回调函数的实现方式进行模块解耦,但是需要引入新的内容,即公共模块Commoon层(包含第三方功能库)。
公共模块主要有各模块都需要使用的类型定义、结构体定义、通用函数或常用宏定义等(通常属于基础类的功能,不会受功能需求和不同平台的影响)。
基于公共模块,为了解决各模块之前的数据交互,可以通过公共模块实现基础类的功能达到各应用层模块解耦的目的。
参考消息队列的方式,可以实现一个生产者/消费者的功能模块(这种可以称作 观察者模式 ,即存在 观察者 和 被观察者 ),即某一模块更新数据后,其他模块可以第一时间得到通知更新(采用回调函数的方式实现)
看图:
Callback 是一个 指针数组变量 ,每个数组成员都是 函数指针类型 的 变量 ,通过函数 Notify_Attach 拿到了应用层代码函数 OnSaveParam(...) 和OnUpdateParam(...)的函数地址,之后人机交互模块调用了 Notify_EventNotify,从而调用 Callback ,调用方式和直接调用 OnFunction(...) 存在些许差异,因为是数组,所有需要 [ ] 取函数地址,为了保证系统运行安全,调用前要确保 Callback[i] 不为 NULL,否则会引起程序异常。
从上述看,也许有人感觉这样处理反而复杂了,直接调用不香吗?(上述人机交互模块属于 被观察者 ,参数和其他模块属于 观察者 )
有以下几个好处:
当然这种方式也有缺点:
避免方式:
事件通知模块头文件
- #ifndef _NOTIFY_H_
- #define _NOTIFY_H_
-
-
- #include <stdint.h>
-
-
- /**
- * @brief 应用模块ID枚举定义
- *
- */
- typedef enum
- {
- NOTIFY_ID_HMI = 0, // 人机交互模块
- NOTIFY_ID_SYS_PARAM, // 参数管理模块
-
- NOTIFY_ID_TOTAL
- } NotifyId_e;
-
- /**
- * @brief 事件类型枚举定义
- *
- */
- typedef enum
- {
- NOTIFY_EVENT_PARAM_UPDATE, // 参数更新事件, 对应结构体 PrramUpdateInfo_t
-
- NOTIFY_EVENT_TOTAL
- } NotifyEvent_e;
-
-
- typedef struct
- {
- uint16_t addr;
- uint32_t param;
- }PrramUpdateInfo_t;
-
-
- typedef int (*EventNotifyCB)(NotifyId_e id, NotifyEvent_e eEvent, const void *pData, uint32_t length);
-
-
- extern void Notify_Init(void);
-
- extern int Notify_Attach(NotifyId_e id, NotifyEvent_e eEvent, EventNotifyCB pfnCallback);
- extern int Notify_Detach(NotifyId_e id, NotifyEvent_e eEvent);
- extern int Notify_EventNotify(NotifyId_e id, NotifyEvent_e eEvent, const void *pData, uint32_t length);
-
- #endif /* _NOTIFY_H_ */
事件通知模块源文件:
- #include "notify.h"
- #include <string.h>
-
- static EventNotifyCB sg_pfnCallback[NOTIFY_ID_TOTAL][NOTIFY_EVENT_TOTAL];
-
- /**
- * @brief 事件初始化
- *
- */
- void Notify_Init(void)
- {
- memset(sg_pfnCallback, 0, sizeof(sg_pfnCallback));
- }
-
- /**
- * @brief 添加事件监听通知
- *
- * @param[in] id 应用模块ID
- * @param[in] eEvent 事件
- * @param[in] pfnCallback 回调函数
- * @return 0,成功; -1,失败
- */
- int Notify_Attach(NotifyId_e id, NotifyEvent_e eEvent, EventNotifyCB pfnCallback)
- {
- if (id >= 0 && id < NOTIFY_ID_TOTAL && eEvent < NOTIFY_EVENT_TOTAL)
- {
- sg_pfnCallback[id][eEvent] = pfnCallback;
- return 0;
- }
-
- return -1;
- }
-
- /**
- * @brief 删除事件监听通知
- *
- * @param[in] id 应用模块ID
- * @param[in] eEvent 事件
- * @return 0,成功; -1,失败
- */
- int Notify_Detach(NotifyId_e id, NotifyEvent_e eEvent)
- {
- if (id >= 0 && id < NOTIFY_ID_TOTAL && eEvent < NOTIFY_EVENT_TOTAL)
- {
- sg_pfnCallback[id][eEvent] = 0;
- return 0;
- }
-
- return -1;
- }
-
- /**
- * @brief 事件通知
- *
- * @param[in] id 应用模块ID
- * @param[in] eEvent 事件类型
- * @param[in] pData 消息内容
- * @param[in] length 消息长度
- * @return 0,成功; -1,失败
- */
- int Notify_EventNotify(NotifyId_e id, NotifyEvent_e eEvent, const void *pData, uint32_t length)
- {
- int i;
-
- if (eEvent < NOTIFY_EVENT_TOTAL)
- {
- for (i = 0; i < NOTIFY_ID_TOTAL; i++)
- {
- if (sg_pfnCallback[i][eEvent] != 0)
- {
- sg_pfnCallback[i][eEvent](id, eEvent, pData, length);
- }
- }
-
- return 0;
- }
-
- return -1;
- }
参数应用层模块:
- #include "notify.h"
-
- static int Param_OnNotifyProc(NotifyId_e id, NotifyEvent_e eEvent, const void *pData, uint32_t length);
-
- void Param_Init(void)
- {
- Notify_Attach(NOTIFY_ID_SYS_PARAM, NOTIFY_EVENT_PARAM_UPDATE, Param_OnNotifyProc);
- }
-
- // 事件回调处理
- int Param_OnNotifyProc(NotifyId_e id, NotifyEvent_e eEvent, const void *pData, uint32_t length)
- {
- switch (eEvent)
- {
- case NOTIFY_EVENT_PARAM_UPDATE:
- {
- PrramUpdateInfo_t *pInfo = (PrramUpdateInfo_t *)pData;
- SaveParam(pInfo->addr, pInfo->param);// 保存参数
- }
- break;
- default:
- break;
- }
-
- return 0;
- }
人机交互应用层模块
- #include "notify.h"
-
- void Hmi_Init(void)
- {
-
- }
-
- // 需要保存参数
- int Hmi_SaveProc(void)
- {
- ParamUpdateInfo_t info;
-
- info.addr = 5;
- info.param = 20;
-
- Notify_EventNotify(NOTIFY_ID_HMI, NOTIFY_EVENT_HMI_UPDATE, &info, sizeof(ParamUpdateInfo_t));
- }