lwevt 核心实现:两次 include 生成事件类型,一次遍历完成广播
声明: lwevt 是一个开源项目,作者为 Tilen MAJERLE。本文是阅读该项目源码和文档后整理的学习笔记,用于理解 X-macro 类型生成与广播分发在嵌入式事件系统里的实现方式。本文作者不是该项目的开发者,未参与该项目的任何代码贡献。 文中所有工程细节均来自对开源代码的分析,不代表本文作者的设计决策。
这篇文章讲 lwevt 源码里的三个实现思想:类型系统交给编译器生成、分发退化成一次数组遍历、状态只有三个静态变量。
核心思想
lwevt 把整个事件系统压到最小:170 B 代码加 48 B RAM。三个落点:
- 类型用 X-macro 生成:声明一次,枚举和 union 同时出来
- 分发用广播:所有监听器收所有事件,过滤下沉到监听器
- 状态用全局单例:一个静态句柄、一个静态监听表、一个计数,无堆无锁
X-macro:两次 include 生成类型系统
X-macro 的核心技巧:宏在 include 之前重新定义,让同一个头文件在两次 include 时展开成不同内容。
lwevt_type.h 先给两个宏提供空默认值,再 include 用户的 lwevt_types.h,最后 undef:
#ifndef LWEVT_TYPE_BASIC
#define LWEVT_TYPE_BASIC(name)
#endif
#ifndef LWEVT_TYPE_EXT
#define LWEVT_TYPE_EXT(name, data)
#endif
#include "lwevt_types.h" /* 用户的事件声明 */
#undef LWEVT_TYPE_BASIC
#undef LWEVT_TYPE_EXTlwevt.h 第一次 include 前把宏定义成生成枚举成员,第二次定义成生成 union 成员:
/* 第一次:枚举 */
#define LWEVT_TYPE_BASIC(name) name,
#define LWEVT_TYPE_EXT(name, data) name,
#include "lwevt/lwevt_type.h"
/* 第二次:union */
#define LWEVT_TYPE_EXT(name, data) data;
#include "lwevt/lwevt_type.h"用户在 lwevt_types.h 里只写一行声明:
LWEVT_TYPE_EXT(LWEVT_TYPE_TEMP_READY, struct { int16_t temp_x10; } temp)编译后同时得到枚举成员 LWEVT_TYPE_TEMP_READY 和 union 成员 temp,类型与数据绑定,改一处两端生效。
广播分发:一次遍历调用全部监听器
lwevt_dispatch_ex 只有两个动作:
evt_handle->type = typefor (idx = 0; idx < evt_fncs_cnt; ++idx) evt_fncs[idx](evt_handle)
没有话题过滤,没有匹配逻辑。每个监听器收到每个事件,自己用 switch 或 if 判断 type。数据零拷贝:事件数据就在句柄的 union 里,监听器直接读写。
注册去重与全局单例
lwevt_register先遍历现有监听表查重,同一个函数指针直接返回 0,避免重复注册后每个事件触发两次- 状态是三个 static:默认句柄
evt、监听表evt_fncs[8]、计数evt_fncs_cnt。LWEVT_CFG_ENABLE_DEFAULT_HANDLE=0时去掉默认句柄,改用lwevt_dispatch_ex传自己的句柄,再省 12 B - 没有退订接口:数组只追加不删除,这是把接口压到最小的取舍
设计取舍与边界
- 广播换简单:遍历没有匹配开销,代价是每个监听器都要处理无关事件
- 类型生成换手感:X-macro 让事件声明集中在一处,代价是读代码要先理解宏展开
- 单例换零成本:无堆无锁,代价是只能有一套监听表、无法退订
- 边界:dispatch 同步执行,在中断里调用时回调也在中断上下文运行