- 嵌入式
- 系统编程
【免费下载链接】fprime
F´ - A flight software and embedded systems framework
本文以 F´(Flight Software Framework)仓库中的 Fw/Obj/docs/sdd.md 设计说明文档为主体,结合
Fw::ObjBase、Fw::ObjRegistry的源码实现、FpConfig.hpp编译开关以及拓扑自动生成代码,系统讲解 F´ 对象体系的根节点设计、对象命名存储、可选的对象注册机制及其工程实践。读完本文,你将掌握Fw::Obj模块的类层次定位、三个核心配置宏(FW_OBJECT_NAMES、FW_OBJECT_TO_STRING、FW_OBJECT_REGISTRATION)的作用与取值影响,以及如何利用SimpleObjRegistry在运行时转储全系统对象清单用于调试。
1. 模块概览与架构定位
Fw::Obj是 F´ 体系中最基础的模块之一。根据 Fw/Obj/docs/sdd.md 的说明,它包含两个关键类型:
Fw::ObjBase:定义 F´ 架构中对象的根类,是所有组件(Component)与端口(Port)的共同基类。它负责存储对象名称,并在已注册对象注册表(object registry)时执行对象注册。Fw::ObjRegistry:对象注册表的纯虚基类。它是一个可选特性,由FW_OBJECT_REGISTRATION宏控制(该宏定义于 config/FpConfig.hpp,原文档写作FpConfig.hpp,在仓库中其路径为config/FpConfig.hpp)。其核心思想是:通过一个注册表跟踪系统中已实例化的所有对象,注册表可以调用Fw::ObjBase的公有接口查询实例信息,而具体如何存储对象指针、如何产出实例数据,由派生类自行实现。
模块的仓库构成非常精简,参见 Fw/Obj/README 与目录结构:
| 文件 | 职责 |
|---|---|
| Fw/Obj/ObjBase.hpp | Fw::ObjBase与Fw::ObjRegistry的类声明 |
| Fw/Obj/ObjBase.cpp | 上述两个类的实现 |
| Fw/Obj/SimpleObjRegistry.hpp | 一个最基础的对象注册表实现(声明) |
| Fw/Obj/SimpleObjRegistry.cpp | 基础对象注册表实现(定义) |
| Fw/Obj/CMakeLists.txt | 模块构建脚本 |
| Fw/Obj/docs/sdd.md | 模块软件设计说明(本文主体) |
需要说明:原 sdd.md 中多处将根类写作
Fw::Object,而当前仓库源码中的类名统一为Fw::ObjBase(见 Fw/Obj/ObjBase.hpp),这是设计文档早期命名与现行实现的差异,本文以源码为准。
2. 核心类 Fw::ObjBase:根类的设计与实现
2.1 类层次中的位置
从源码继承关系可以确认,Fw::ObjBase确实是组件与端口的共同祖先:
- Fw/Port/PortBase.hpp 中
class PortBase : public Fw::ObjBase,所有端口基类都派生自它; - Fw/Comp/PassiveComponentBase.hpp 中
class PassiveComponentBase : public Fw::ObjBase,被动组件基类同样派生自它,其构造函数在 PassiveComponentBase.cpp 中显式调用Fw::ObjBase(name)完成初始化; - Fw/Port/PortBase.cpp 中端口构造时以
Fw::ObjBase(nullptr)初始化,并在后续通过setObjName设置名称——这与ObjBase构造函数对空指针的容错逻辑(见下)相呼应。
也就是说,整个 F´ 系统中每一个组件实例、每一个端口实例,本质上都是一个Fw::ObjBase对象,因此把对象注册表放在这一层,就能无差别地跟踪全部系统对象。
2.2 职责一:对象名称的存储与读写
当FW_OBJECT_NAMES == 1时(默认开启,见 config/FpConfig.hpp),Fw::ObjBase会在每个实例内分配一个固定大小的名字缓冲区m_objName,其大小由FW_OBJ_NAME_MAX_SIZE决定(默认 80 字节,见 config/FpConfig.hpp),并提供两个接口:
const char* getObjName():返回对象名称指针(ObjBase.hpp);void setObjName(const char* name):将传入字符串拷贝进内部缓冲区(ObjBase.hpp)。
其实现细节在 ObjBase.cpp:
void ObjBase::setObjName(const char* name) { strncpy(this->m_objName, name, sizeof(this->m_objName)); this->m_objName[sizeof(this->m_objName)-1] = 0; // 强制以 '\0' 结尾 }注意两点工程细节:
- 使用
strncpy并强制在缓冲区末尾写0,保证超长名称被截断后依然是以 NUL 结尾的合法 C 字符串(FpConfig.hpp注释明确"Larger names will be truncated",即超过 80 字节的名称会被截断); - 构造函数对空指针做了容错:当传入
nullptr时自动命名为"NoName"(ObjBase.cpp)。
当FW_OBJECT_NAMES == 0时,名称缓冲区与相关接口全部被编译掉(构造函数为空实现),以节省内存——适用于存储极度受限的嵌入式目标。此时可配合FW_OPTIONAL_NAME(name)宏(config/FpConfig.hpp),它在名称功能关闭时自动把字符串替换为空串,从而避免无意义的字符串常量占用二进制体积。
2.3 职责二:toString 对象描述
在FW_OBJECT_NAMES == 1且FW_OBJECT_TO_STRING == 1(默认开启,见 config/FpConfig.hpp)时,Fw::ObjBase提供一个虚函数:
virtual void toString(char* str, NATIVE_INT_TYPE size);默认实现(ObjBase.cpp)输出"Obj: <name>"格式的字符串,并对size <= 0的情况使用FW_ASSERT断言保护。该方法的用途是供派生类重写以返回对象自身的描述信息——例如组件可以重写为包含实例号、组件类型等信息的描述。目标缓冲区大小由FW_OBJ_TO_STRING_BUFFER_SIZE配置(默认 255 字节,见 config/FpConfig.hpp)。注意该宏只有在FW_OBJECT_NAMES开启时才会生效,二者存在依赖关系。
3. 对象注册机制 Fw::ObjRegistry 与注册流程
3.1 编译开关与纯虚基类
对象注册是可选的,由FW_OBJECT_REGISTRATION宏控制(默认开启,见 config/FpConfig.hpp):
#ifndef FW_OBJECT_REGISTRATION #define FW_OBJECT_REGISTRATION 1 // 是否允许对象集中注册(更多代码、更强的对象跟踪能力) #endif当该宏为 1 时,Fw/Obj/ObjBase.hpp 中定义纯虚基类:
class ObjRegistry { public: virtual void regObject(ObjBase* obj) = 0; // 对象注册时被调用的纯虚函数 virtual ~ObjRegistry(); };regObject是注册表的唯一纯虚接口:它通过ObjBase::setObjRegistry()设置的静态指针被调用,传入对象实例指针,至于如何使用该指针完全由派生注册表决定——这正是原 sdd.md 强调的"实际存储方法与数据产出方式留给派生类"的设计。
3.2 注册调用链:setObjRegistry → init → regObject
Fw::ObjBase内部持有一个静态指针s_objRegistry(ObjBase.hpp),并提供静态函数setObjRegistry(ObjRegistry* reg)来设置它(ObjBase.hpp,实现见 ObjBase.cpp)。
每个对象初始化时调用受保护的init()(ObjBase.hpp),其实现(ObjBase.cpp):
void ObjBase::init() { #if FW_OBJECT_REGISTRATION if (ObjBase::s_objRegistry) { ObjBase::s_objRegistry->regObject(this); } #endif }由此形成完整的注册调用链:
SimpleObjRegistry 构造(或用户代码)→ ObjBase::setObjRegistry(®istry) → 各组件/端口构造完成后调用 ObjBase::init() → s_objRegistry 非空 → registry->regObject(this) // 对象被登记原文档与头文件注释还明确给出一个重要限制:setObjRegistry的调用"可能不可重入、非线程安全",且框架自带的SimpleObjRegistry就是不可重入的(ObjBase.hpp)。因此注册表实例化应放在启动早期的单线程上下文中完成。
3.3 配置宏速查表
与Fw::Obj相关的全部配置宏集中在 config/FpConfig.hpp,整理如下:
| 宏 | 默认值 | 作用 | 位置 |
|---|---|---|---|
FW_OBJECT_NAMES | 1 | 是否为对象存储名称(更多内存,可用于对象跟踪) | FpConfig.hpp |
FW_OBJECT_TO_STRING | 1(依赖FW_OBJECT_NAMES) | 是否生成toString()方法转储对象内部信息(更多代码) | FpConfig.hpp |
FW_OBJECT_REGISTRATION | 1 | 是否启用对象集中注册(更多代码、更强对象跟踪) | FpConfig.hpp |
FW_OBJ_NAME_MAX_SIZE | 80 | 对象名缓冲区字节数,超长截断 | FpConfig.hpp |
FW_OBJ_TO_STRING_BUFFER_SIZE | 255 | toString()输出缓冲区大小 | FpConfig.hpp |
FW_OBJ_SIMPLE_REG_ENTRIES | 500 | 简单注册表可存储的对象数量上限 | FpConfig.hpp |
FW_OBJ_SIMPLE_REG_BUFF_SIZE | 255 | 转储对象名/描述时的缓冲区大小(应 ≥FW_OBJ_NAME_MAX_SIZE) | FpConfig.hpp |
可见这些宏之间存在依赖链:FW_OBJECT_TO_STRING依赖FW_OBJECT_NAMES,注册表相关容量宏依赖FW_OBJECT_REGISTRATION。为降低嵌入式二进制体积,可将三者同时置 0,但会失去对象跟踪与转储能力。
4. 参考实现 SimpleObjRegistry:默认对象注册表
SimpleObjRegistry是框架提供的最基本注册表实现(Fw/Obj/SimpleObjRegistry.hpp),其头文件注释明确它是"默认实现与示例":实例化时它会用setObjRegistry()把自己注册进ObjBase的静态指针,此后对象随实例化过程逐个登记,并可通过dump()列出注册表中的对象。
4.1 自注册的生命周期
构造函数(SimpleObjRegistry.cpp):
SimpleObjRegistry::SimpleObjRegistry() { ObjBase::setObjRegistry(this); // 将自己设为全局注册表 this->m_numEntries = 0; for (NATIVE_INT_TYPE entry = 0; entry < FW_OBJ_SIMPLE_REG_ENTRIES; entry++) { this->m_objPtrArray[entry] = nullptr; } }析构函数则调用ObjBase::setObjRegistry(nullptr)解除注册(SimpleObjRegistry.cpp)。存储结构是一个定长指针数组m_objPtrArray[FW_OBJ_SIMPLE_REG_ENTRIES](默认 500 个槽位),regObject实现为顺序追加并带断言保护:
void SimpleObjRegistry::regObject(ObjBase* obj) { FW_ASSERT(this->m_numEntries < FW_OBJ_SIMPLE_REG_ENTRIES); // 槽位耗尽即断言失败 this->m_objPtrArray[this->m_numEntries++] = obj; }这意味着当系统对象总数超过 500 时,FW_ASSERT会直接触发断言——这是嵌入式框架"宁可失败也不要静默越界"的典型做法。若部署的系统对象较多,应相应调大FW_OBJ_SIMPLE_REG_ENTRIES。
4.2 转储(dump)与清理(clear)
dump()遍历已登记对象,通过Fw::Logger::logMsg打印每个对象的序号、指针地址及描述(SimpleObjRegistry.cpp)。输出内容随编译开关组合变化:
FW_OBJECT_NAMES + FW_OBJECT_TO_STRING开启:调用toString(),输出形如Entry: %d Ptr: %p Str: %s;- 仅
FW_OBJECT_NAMES开启:调用getObjName(),输出Entry: %d Ptr: %p Name: %s; - 两者皆关:仅输出
Entry: %d Ptr: %p。
当FW_OBJECT_NAMES == 1时还提供按名称定向转储的dump(const char* objName)(SimpleObjRegistry.cpp),用strncmp精确匹配对象名后打印;clear()则简单地将计数清零(SimpleObjRegistry.cpp),实现"软清空"。
5. 在 F´ 工程中的实际应用:拓扑自动生成与调试入口
5.1 拓扑代码生成中的注册表实例化
F´ 的部署工程通常通过 FPP 建模生成拓扑代码,自动生成模板会默认实例化一个SimpleObjRegistry。以 publicTopologyCpp.tmpl 为例:
simpleReg_ptr = new Fw::SimpleObjRegistry();模板随后在构造完所有组件、完成端口连接并启动活动组件之后调用dumparch()(publicTopologyCpp.tmpl)。而dumparch()与dumpobj()这两个 C 链接函数由 includes1TopologyCpp.tmpl 生成:
extern "C" { void dumparch(); void dumpobj(const char* objName); } void dumparch() { simpleReg_ptr->dump(); } void dumpobj(const char* objName) { simpleReg_ptr->dump(objName); }也就是说,每个由自动生成模板搭建的 F´ 部署,天然具备两个调试入口:
dumparch():转储整个架构中已注册的全部对象(数量上限 500);dumpobj("<name>"):按名称查询并转储单个对象。
这一机制让开发者无需额外打日志,即可在运行时检查所有组件与端口实例是否按预期创建、名称是否正确。实际部署代码中也能看到SimpleObjRegistry的直接引用,例如 RPI/Top/Components.hpp 在树莓派示例部署中#include <Fw/Obj/SimpleObjRegistry.hpp>,其拓扑文件会生成对应extern实例。
5.2 组件/端口如何"被登记"
由于所有组件(PassiveComponentBase等)与端口(PortBase)的基类都是Fw::ObjBase,且构造函数链最终调用ObjBase构造函数、初始化阶段调用init(),因此只要注册表已设置,系统中每个组件与端口实例都会被自动登记,无需组件作者编写任何额外代码。这正是 sdd.md 所说"对象初始化时执行注册"(register objects when they are initialized)在框架层的落地。
6. 使用建议与注意事项
- 注册表应尽早、单线程地建立:
setObjRegistry不可重入且非线程安全(ObjBase.hpp),推荐在主函数早期、组件实例化之前完成SimpleObjRegistry的创建。 - 容量与资源权衡:
FW_OBJ_SIMPLE_REG_ENTRIES(默认 500)与FW_OBJ_NAME_MAX_SIZE(默认 80)会随对象数量线性占用内存;小型嵌入式目标可关闭FW_OBJECT_NAMES/FW_OBJECT_TO_STRING/FW_OBJECT_REGISTRATION换取体积,但会失去dumparch()调试能力。 - 名称截断是预期行为:
setObjName对超长名称静默截断并保证 NUL 结尾,若需要更长的对象名请调整FW_OBJ_NAME_MAX_SIZE,且注意FW_OBJ_SIMPLE_REG_BUFF_SIZE应不小于名称长度,避免转储缓冲区溢出。 - 注册表溢出即断言:
SimpleObjRegistry槽位耗尽会触发FW_ASSERT,这是框架"显式失败优于静默错误"的工程选择,部署前应核对系统对象总数。 toString是可扩展点:派生类可通过重写toString提供比默认"Obj: <name>"更丰富的对象描述(如实例号、类型、关键状态),从而让dumparch()输出更有诊断价值。
7. 变更日志
根据 Fw/Obj/docs/sdd.md 记录的模块设计文档变更历史:
| 日期 | 说明 |
|---|---|
| 2016-04-24 | 初始版本 |
该模块自 2016 年随 F´ 早期版本(当时写作 ISF)引入,核心设计——根类 + 可选注册表——沿用至今,是 F´ 对象体系稳定性的直接体现。仓库中ObjBase.hpp/.cpp的版权头(Copyright 2016, California Institute of Technology)与文档时间线一致,进一步印证了其设计演进脉络。
- 嵌入式
- 系统编程
【免费下载链接】fprime
F´ - A flight software and embedded systems framework
相关推荐
F´ 框架的 Fw::Obj 模块深度解析:对象基类与对象注册机制
F´ 框架的 Fw::Obj 模块深度解析:对象基类与对象注册机制 导读 Fw::Obj 是 F´(F Prime)飞行软件与嵌入式系统框架中定义 对象体系根基
嵌入式系统编程F´ 框架对象体系解析:Fw::ObjBase 与 Fw::ObjRegistry 对象注册机制完全指南
F´ 框架对象体系解析:Fw::ObjBase 与 Fw::ObjRegistry 对象注册机制完全指南 导读 Fw::Obj 是 F´(F Prime)飞行软
嵌入式系统编程F´ 框架 Fw::Port 端口基类深度解析:输入/输出端口、对象注册与端口追踪机制
F´ 框架 Fw::Port 端口基类深度解析:输入/输出端口、对象注册与端口追踪机制 导读 本文围绕 F´(F Prime)飞行软件与嵌入式系统框架中的 Fw
嵌入式系统编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考