尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Serial Studio 模块化核心重构:基于 core/ 分层静态库与消息总线的架构治理实战

Serial Studio 模块化核心重构:基于 core/ 分层静态库与消息总线的架构治理实战 Serial Studio 模块化核心重构基于 core/ 分层静态库与消息总线的架构治理实战【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio本文以仓库内设计文档 doc/claude/specs/0076-modular-core/plan.md 为骨架讲解 Serial Studio开源遥测仪表板支持 UART、BLE、MQTT、Modbus、CAN 等数据源如何把单一巨型可执行文件拆分为core/下的分层静态库并用scripts/layer-verify.py把依赖只许向下变成 CI 里机械强制的门禁文中还包含 Stage 2 修订中为打破跨层单例耦合而设计的Core::Bus::MessageBus进程内消息总线的完整设计。读完你既能复现这套库拆分 分层门禁 消息总线的改造方法也能理解其背后的构建顺序隔离、Pro 代码门控与静态库链接陷阱。背景为什么要给一个能跑的巨构做模块化Serial Studio 的app/src/长期是一个单体的编译单元集合驱动、数据管线、存储、API、UI 全部打进同一个可执行目标源文件通过include_directories(src)共享同一套相对 include 根。这种结构的典型症状——任意模块可以互相 include、跨模块状态靠访问他人单例X::instance()、测试不得不把生产.cpp重新编译一遍——在这里同样存在。计划文档将改造定义为4 个阶段中的第 2 阶段Phase 2 of 4the HOW于 2026-09-03 获批由维护者授权、代号 Fable 的经理负责设计、共享文件与评审。第 1 阶段是问题审计第 2 阶段给出 HOW第 3/4 阶段是逐条消灭遗留依赖。改造的核心约束有三条贯穿全文不改变任何运行时行为所有被移动的文件逐字搬迁git mv include 路径改写不允许任何定义、签名或命名空间的变化隔离来自构建顺序而非代码规范库在app/CMakeLists.txt中于 Qt 发现之后、include_directories(src)之前加入因此库翻译单元在结构上就看不到app/src让分层成为一个棘轮ratchet新写的scripts/layer-verify.py机械地检查每一次 include 与 CMake 条目违反即失败防止分层随时间腐烂。总体方案两个静态库与构建顺序隔离方案只引入两个新的静态 Qt 库位于新的顶层目录core/库目录别名职责SerialStudioCorecore/Core/SerialStudio::Core依赖最轻的公共地基断言、热路径宏、SIMD 内核、校验和、SPSC 环形缓冲、解析预算、异步任务树SerialStudioProtocolscore/Protocols/SerialStudio::Protocols纯 wire-format 编解码器字节进、类型化结果出公开链接Core关键顺序是add_subdirectory(core)必须放在qt_policy(SET QTP0004 NEW)之后、include_directories(src)之前。依赖隔离是这一顺序的副产品——库目标在app/src进入 include 搜索路径之前就被定义所以库翻译单元在结构上不可能 include 任何app/src下的头文件可执行文件和单元/模糊测试目标链接这两个库并获得core/作为公共 include 根#include Core/…、#include Protocols/…。目录名即 include 命名空间core/是根Core与Protocols通过目录前缀被引用。当前仓库 core/CMakeLists.txt 的开头注释忠实地记录了这一设计意图并给出了最终的分层依赖图含后续阶段Ui - Api - Storage - Pipeline - Protocols - Core - Qt6::Core Devices -----^ (Pipeline 的兄弟层Api 与 Ui 同时看到两者)依赖只许向下每个库只链接其下层且永远不 includeapp/src中的任何内容跨库状态走Core::Bus::MessageBus保留主题或由组合根绑定的接口任何库不得构造或触达另一个库的单例。第一阶段搬迁清单Core13 个文件与 Protocols26 个文件Core依赖最轻的地基app/src/中恰好有 13 个 GPL 基础文件搬入core/Core/include 统一改写为Core/…前缀来源app/src/去向core/Core/include 变为SSAssert.h/SSAssert.cppSSAssert.h/.cppCore/SSAssert.hConcepts.hConcepts.hCore/Concepts.hDSPSimd.hDSPSimd.hCore/DSPSimd.hDataModel/HotpathOptimization.hHotpathOptimization.hCore/HotpathOptimization.hDataModel/ParseBudget.hParseBudget.hCore/ParseBudget.hIO/CircularBuffer.hCircularBuffer.hCore/CircularBuffer.hIO/Checksum.h/.cppChecksum.h/.cppCore/Checksum.hAsync/AsyncClock.h、Async/RetryPolicy.h/.cpp、Async/TaskTree.h/.cppAsync/…Core/Async/…从当前仓库 core/Core/CMakeLists.txt 可以看到该库的实际形态超出计划之初的 13 个文件后续阶段进一步充实了它qt_add_library(SerialStudioCore STATIC …)建库add_library(SerialStudio::Core ALIAS SerialStudioCore)提供别名target_include_directories(SerialStudioCore PUBLIC ${CMAKE_SOURCE_DIR}/core)把core/暴露为公共 include 根target_link_libraries(SerialStudioCore PUBLIC Qt6::Core)——Qt Core 是它的全部链接集闭包内唯一的几何类型QPointF也属于 QtCore因此该库不触达 Gui、Network 或任何总线模块。Core还承担了一个微妙的职责Licensing/CommercialToken.{h,cpp}商业能力令牌HMAC 密封所有 Pro 守卫点读取它在if(BUILD_COMMERCIAL)下被追加进该库因为令牌只依赖 QtCore 与生成的守卫头文件这正好满足守卫点保持原样、库不需要 app/src的条件。Protocols纯编解码器与 Pro 门控app/src/中 26 个文件搬入core/Protocols/。其中 18 个属于Pro 门控if(BUILD_COMMERCIAL)块内计划文档注明位于当时app/CMakeLists.txt的 1290–1334 行12 个是 GPL 基础来源app/src/IO/…等去向core/Protocols/门控Drivers/CANBus/CanReassembly.h/.cpp、CANBus/GsUsbProtocol.hCAN/…ProDrivers/S7/IsoTsap.h/.cpp、S7/S7Pdu.h/.cpp、Drivers/S7Address.h/.cppS7/…ProDrivers/Iec104/Apci.h/.cpp、Asdu.h/.cppIec104/…ProDrivers/MQTT/SparkplugPayload.h/.cppSparkplug/SparkplugPayload.h/.cppProDrivers/Modbus/ModbusRtuCodec.h/.cppModbus/…ProFileTransmission/CRC.h、Protocol.h、XMODEM.h/.cpp、YMODEM.h/.cpp、ZMODEM.h/.cppFileTransfer/…GPL 基础Pro 门控的机制值得展开这些.cpp原在可执行文件的if(BUILD_COMMERCIAL)块中迁入Protocols后改为在if(BUILD_COMMERCIAL OR SS_BUILD_TESTS)下加入——这是open62541的先例原文档引用的 lib/CMakeLists.txt。原因很精妙GPL 单元测试层仍然编译它们CI 中的单元/模糊测试本来就是 GPL configure测试要验证这些编解码器就必须能编译GPL 应用二进制依然不链接它们静态归档只贡献被引用的目标objectGPL 构建中没有任何东西引用这些符号所以它们不会进入最终二进制。当前 core/Protocols/CMakeLists.txt 实现了这一门控GPL 集的FileTransfer/五个协议文件常驻Pro 集在if(BUILD_COMMERCIAL OR SS_BUILD_TESTS)下加入 CAN/S7/IEC 104/Sparkplug/Modbus/TLS 编解码器且target_link_libraries(SerialStudioProtocols PUBLIC Qt6::Network)只在此分支内——因为唯一例外是 Pro 的 TLS 身份/配置对Tls/TlsIdentity、Tls/TlsConfigMQTT 驱动与 MQTT 发布器都要用它构造QSslConfiguration需要 Qt Network。注释中一句话点明了这一层的本质纯编解码器——字节进、类型化结果出不碰 socket、对话框、设置或单例这也是为什么每个编解码器都被单元或模糊目标覆盖、现在改为链接归档而非重编译同一翻译单元。头文件随源文件列出的原因moc 只扫描目标列出的内容Core的 CMake 特意把头文件与源文件并列列出并在注释中解释了原因AUTOMOC 只扫描目标列出的文件而Async/TaskTree.h携带 9 个Q_OBJECT类Bus/MessageBus.h还有一个。这些头文件绝不能同时留在可执行文件的HEADERS列表中否则 moc 会跑两遍链接时出现重复的staticMetaObject符号。这正是门禁检查 5moc-double-listed要杜绝的错误。架构与数据流构建图视角计划文档明确声明无任何运行时数据流变化。改造只改变构建图SerialStudio (exe) ──links──▶ SerialStudio::Protocols ──PUBLIC──▶ SerialStudio::Core ──▶ Qt6::Core tst_* / fuzz_* ──links──▶ (same targets)core/是两个库共同的 include 根Core只见core/与 QtProtocols见core/与 Qt可执行文件保留include_directories(src)用于自己的树并通过 PUBLIC 链接传递获得core/。add_subdirectory(core)放在app/CMakeLists.txt而非根 CMakeLists.txt 是刻意为之根目录从不调用find_package(Qt6)或qt_standard_project_setup()。放在 Qt 发现之后、include_directories(src)之前加入core让库获得Qt 与CMAKE_AUTOMOCQt 6 的qt_add_library即 qt_standard_project_setup 体系的一部分根目录目录级作用域的全局优化/SIMD/PGO/sanitizer 标志与BUILD_COMMERCIAL同时被结构性地剥夺app/src。热路径与线程影响计划文档对热路径给出了逐项确认是否触碰热路径只移动了头文件内核DSPSimd.h、HotpathOptimization.h、CircularBuffer.h、SSAssert.h与Checksum.cpp全部逐字搬迁。它们原本各自就是独立的翻译单元或头文件全局-O3/-msse4.1/LTO/PGO 标志是目录级作用域自然到达新目标。内联行为不变默认不开启 unity build。--benchmark-hotpath是维护者每日早间的验收门。新的跨线程 signal/slot无。热路径缓存标志的新输入无。时间戳所有权Timestamp ownership——不动。数据模型与持久化、API/SDK 表面、QML/UI 在本阶段全部声明为无改动。分层门禁scripts/layer-verify.py分层要存活就必须机械化一旦 include 根重叠构建本身并不会阻止core/翻译单元 includeapp/src/...也不会阻止被移动的头文件残留在可执行文件的HEADERS列表里被 moc 两遍。因此计划新增 scripts/layer-verify.py配置直接内联在脚本顶部的表中不引入新的 JSON 文件LAYERS {Core: [], Protocols: [Core]} # 下层各层可被 include APP_ROOTS (app/src, app/tests) # 可以 include 任何东西五条检查各算一个错误include-unresolved——core/**与app/src/**、app/tests/**.h/.cpp/.mm/.c中的每个#include …都必须能针对 include 者所在目录、core/或app/src解析成功Qt/系统尖括号 include 跳过layer-upward——core/Layer/下的文件不得 includecore/Layer/、core/Allowed/或 Qt/系统之外的路径core-unowned——core/下的.cpp/.h要么不在任何core/**/CMakeLists.txt中、要么被多于一个目标列出两种情况都算错cmake-missing——app/CMakeLists.txt中的src/…条目、app/tests/CMakeLists.txt中的${SS_APP_SRC}/…条目、或core/**/CMakeLists.txt中的任何条目所指向的文件不存在moc-double-listed——core/下的头文件仍出现在app/CMakeLists.txt中会导致双重 moc。--json供 CI 使用任何错误时退出码为 1。计划文档将此门禁登记在 doc/claude/scripts.md。当前实现从棘轮到全严格当前仓库中的 scripts/layer-verify.py 是计划落地并随 spec 0077 演进的版本值得对照阅读。它的LAYERS表已经扩展为完整七层加 App/TestsLAYERS { Core: (), # 只许见 Qt 与自身 Protocols: (Core,), Pipeline: (Core, Protocols), Devices: (Core, Protocols), Storage: (Core, Protocols, Pipeline), Api: (Core, Pipeline, Devices, Storage), Ui: (Core, Pipeline, Devices, Storage, Api), App: (…全部下层…), Tests: (…全部层…), }DEBT_LAYERS目前为空元组脚本注释说明自 spec 0077 起每一层都是 STRICT任何图外 include 都是错误而非债务棘轮机制layer-debt-growth、基线 scripts/layer-baseline.json作为机制保留但基线上已无任何边。检查项也从计划的 5 条扩展为 9 条新增了include-ambiguous——同一 include 在多个 include 根下解析到不同文件分区库保留原相对路径只有当没有两个根持有同一相对路径时才无歧义cmake-root-violation——core/Layer/CMakeLists.txt命名了图外的 include 根或SerialStudio::链接或根列表把分区库互相链接pair-split——.cpp与其兄弟.h被列在不同 CMakeListsAUTOMOC 会把头文件 moc 两遍。脚本对 CMake 侧同样把关core/Layer/CMakeLists.txt只许把自己的目录及其可 include 层的根命名为 include 根、只许链接这些层的SerialStudio::目标PUBLIC 链接会传递闭包生成的头文件*.moc、protoc 的*.pb.h、LicenseGuards.generated.h按模式放行。CLI 用法python3 scripts/layer-verify.py # 纯文本报告 python3 scripts/layer-verify.py --verbose # 额外输出逐边债务表 python3 scripts/layer-verify.py --json # 机器可读CI python3 scripts/layer-verify.py --accept # 重新播种 scripts/layer-baseline.json # 退出码0 干净1 有错误2 参数错误--accept并非无条件重播种只要还存在include-unresolved/include-ambiguous/layer-upward/cmake-root-violation之一重播种就会被拒绝。方案权衡与替代方案计划文档用一张决策表记录了每个关键抉择这些理由对任何想复制这套架构的人都极具参考价值决策点选项选择与理由根目录名core/、libs/、src/core/——维护者指示目标命名ss_core、Core、SerialStudioCore 别名SerialStudioCore 命名空间别名按指示用 CamelCase且不与 Qt 的Core冲突include 前缀Core/…根 core/vsSSAssert.h根 core/CoreCore/…——层级在每个 include 点可见门禁可读取add_subdirectory(core)位置根 CMakeLists需自建find_package(Qt6)vsapp/内、include_directories(src)前app/内——一次 Qt 发现、一个策略块include 隔离由顺序自然产生帧值类型Frame现在搬 vs 推迟推迟——见下文OpcUaMarshal、OpcUaWire.h、SparkplugSession搬留下OpcUaWire.hincludeSerialStudio.hSparkplugSession.hincludeOpcUaWire.hOpcUaMarshal需要门控的open62541链接。后续处理旧路径的转发 shim 头保留一个版本 vs 不留不留 shim——include 改写是穷尽且可验证的shim 会让分层腐烂单元测试继续重编译.cppvs 链接库链接库——评审者的既定目标也是本次改造的意义所在include 改写方式手改 300 个文件 vs 对 20 种已知 include 形式做精确字符串sedsed处理字面 include 行样板而非代码随后用解析器检查 diff 评审每个被移动文件在git diff -M下必须只显示 include 行变化Pro 编解码器在 GPL 构建中总是编译 vs 门控门控于BUILD_COMMERCIAL OR SS_BUILD_TESTS沿用既有先例不留 shim与测试改为链接库两条尤其值得注意——它们把短期便利让位于长期防腐烂是本次改造的方法论核心。推迟项Frame 进入 Core后续 spec计划文档对DataModel/Frame.cpp编译闭包的审计显示帧类型无法随 Phase 2 一起搬入 Core原因如下Frame.cpp→SerialStudio.h一个带Q_ENUM的QObject反向 includeFrame.h和AppInfo.h使用SerialStudio::toDouble、resolveEscapeSequences、encodeText、TextEncoding、commercialCfg许可Generated/DatasetSerialization.cpp由scripts/generate-property-registry.py生成→DataModel/Project/PropertyHooks.h→DataModel/ProjectModel.hProject/PropertyValidators.cpp→PropertyHooks.h、SerialStudio.h、QColorSerialStudioFrameSupport.cpp→SerialStudio.h、QtCore5Compat。因此搬Frame意味着把SerialStudio拆成 Core 枚举持有者加一个 app 类、把PropertyHooks.h与ProjectModel.h切断、迁移commercialCfg、重指生成器——这是需要编译器在环的语义工作必须独立成 spec。评审者要求的头文件拆分Action/Dataset/Group/Frame/Workspace FrameJson也随该 spec 一起进行。从当前仓库结构看这一后续工作已经完成core/Core/DataModel/Frame.h已存在于 core/Core/CMakeLists.txt 的源列表中计划中帧值类型留在 app/src/DataModel的临时安排已被后续 spec 取代。风险与缓解措施计划文档为每次移动列了八类风险及对策其中前三项是所有把代码搬进静态库工程的通用清单被移动的.cpp依赖了搬迁后不再获得的传递 include——文件原样搬并自带 include每个被移动单元在测试层早已能单独编译测试注册就是证明解析器检查确认每一行 include 都能解析。双重 moc——带Q_OBJECT的头Protocol.h、XMODEM.h、YMODEM.h、ZMODEM.h、TaskTree.h若残留在可执行文件的HEADERS列表会产出重复staticMetaObject符号。由门禁检查 5 拦截。测试既链接库又重编译被移动的.cpp——重复符号。任务中的 grep 验证没有${SS_APP_SRC}/…对被移动文件的引用幸存。库漏掉全局标志——所有标志模块都是根目录作用域按目标的加固显式施加。serial_studio_sign与target_link_mimalloc是链接期、仅可执行文件。被移动文件中的tr()字符串从lupdate消失——translation_manager.py也遍历core/。Lint 门禁对core/失明——code-verify.py/claim-verify.py的根扩展layer-verify.py拥有新不变量。基线变动掩盖增长——普查表只在其它门禁全绿后重播种且 JSON 的 diff 只评审移动键。不可验证的残留——当晚没有编译器运行。维护者的首次构建是最终门禁AC6/AC7handoff.md记录确切的 configure 命令和三种移动可能产生的错误形态缺 include、重复符号、未解析符号的排查法。验证计划静态当晚python scripts/layer-verify.py python scripts/code-verify.py --check python scripts/code-verify.py --tu-census --check python scripts/code-verify.py --dup-census --check python scripts/code-verify.py --singleton-census --check python scripts/claim-verify.py python scripts/registry-verify.py python scripts/documentation-verify.py reuse lint # 若已安装 git diff -M --stat # 每个被移动文件显示为 rename且只有 include 行变化 python scripts/sanitize-commit.py单元当晚pytest tests/scripts/JS 解析器不受影响作为健全性底线运行。维护者次日早晨以-DSS_BUILD_TESTSONconfigure 构建 GPL 版本并ctest一次 Pro configure 构建--benchmark-hotpath启动应用并打开一个项目。Stage 2 修订五个分区库与消息总线2026-09-04 修正案计划文档的第二大部分是次日2026-09-04的修订把改造推进到把app/src剩余子系统整体搬入五个分区库并用消息总线取代跨层单例访问。当前仓库中 core/CMakeLists.txt 已经add_subdirectory了全部七个库Core、Protocols、Pipeline、Devices、Storage、Api、Ui证明该修订已落地。方法整目录 git mv 与棘轮-严格两阶段其余app/src子系统按整目录搬入core/Layer/并保持内部路径不变如core/Pipeline/DataModel/Frame.h、core/Devices/IO/Drivers/UART.h。每个分区目录是一个 include 根app/src也是因此树中每一行#include保持逐字节不变移动是约 1000 个文件的纯git mv。五个目标声明的依赖图是代码当前实际具有的图存在经单例的环所以 CMake 对 GNU ld/lld 重复归档layer-verify.py按有向边计量向上的 include 并与签入基线比对、增长即失败而Core/Protocols保持严格。评审者的只许向下图是基线棘轮趋向的目标每个后续 spec 通过把边上的单例触及替换为总线主题把一条边驱动到零再将该边从棘轮翻转为严格。分区表库目录内容源自app/srcSerialStudio::Pipelinecore/Pipeline/DataModel/整目录、DSP.h、DSPDownsample.h、IO/FrameReader.*、IO/PipelineHost.*、IO/StreamWorker.*、IO/FrameConfig.h、Platform/AppPlatform.*SerialStudio::Devicescore/Devices/IO/其余驱动、ConnectionManager、DeviceManager、HAL_Driver、AsyncTcpDial、FileTransmission 门面、MQTT/SerialStudio::Storagecore/Storage/CSV/、MDF4/、Sessions/、InfluxDB/SerialStudio::Apicore/Api/API/除API/GRPC/SerialStudio::Uicore/Ui/UI/、Console/、Platform/其余、AI/、Misc/除ModuleManager.*与CLI/可执行文件app/srcapp/src/main.cpp、SerialStudio.*、SerialStudioFrameSupport.cpp、AppState.*、AppInfo.h、SessionContext.*、Misc/ModuleManager.*、Misc/CLI/、Licensing/、SelfTest/、Benchmark/、ThirdParty/、API/GRPC/其 protoc 自定义命令随行保留移动前的 include 边普查跨越边界的带引号 include棘轮基线在移动后播种Pipeline→Ui 99 Pipeline→App 91 Pipeline→Devices 50 Ui→Pipeline 93 Ui→App 75 Api→Pipeline 89 Api→App 37 Devices→Pipeline 58 Devices→App 44 Devices→Ui 36 Storage→Pipeline 63 Storage→App 35 … (完整表见 scripts/layer-baseline.json)App边是SerialStudio.h/AppState.h/SessionContext.h/Misc/ModuleManager.h的 include——即从下方触及的组合根它们是可计数的单例地狱。当前 scripts/layer-baseline.json 的存在与此吻合自 spec 0077 起边数为空。Stage 2 的 CMake 机制core/CMakeLists.txt添加全部七个库仍由app/CMakeLists.txt在include_directories(src)之前调用Core/Protocols只见core/。每个分区库声明target_include_directories(PUBLIC core/self core/every partition app/src [license-guards 目录] [gRPC 生成目录——Api 在 ENABLE_GRPC 下])——债务按目标显式写出而不是碰巧继承。链接集每个分区库 PUBLIC 链接${QT_LIBS}以及可执行文件调用的同一组三方辅助target_link_open62541、target_link_openssl、target_link_libplctag、luajit、kissfft、QCodeEditor、QSimpleUpdater、mdflib、usb/hidapi 在其门控下——对静态归档过度链接在最终链接时零成本而这些辅助添加的 include 目录正是编译所需。五个分区之间的互 PUBLIC 链接那个环让 CMake 发出 GNU ld/lld 需要的归档重复。任何被移动文件读取的可执行文件级设置也施加到库上SS_INAPP_TESTS、NativeWindow_macOS.mm的 unity 跳过、SS_UNITY_BUILD下的/bigobj、serial_studio_harden、UNITY_BUILD。门控按库从可执行文件的清单逐条复现if(BUILD_COMMERCIAL)、WIN32/APPLE/UNIX、WITH_WEBENGINE、ENABLE_GRPC。一个草稿脚本解析旧清单与新库清单断言(file, condition)集合守恒AC10。测试继续重编译它们的.cpp清单现在经${SS_CORE_SRC}/Layer/…链接分区归档会把单例闭包拖进来。app/CMakeLists.txt在src之后用include_directories()加入分区根使可执行文件与测试能解析不变的相对 include。消息总线core/Core/Bus/分区库搬完只是债务被数清消灭跨层单例依赖靠的是 core/Core/Bus/ 下的进程内消息总线。它由三个文件构成MessageBus.h /MessageBus.cpp——发布/订阅总线Subscription.h /Subscription.cpp——RAII 句柄Messages.h——共享词汇表the DBC只含 Core/Qt-Core 类型的普通结构体因此一个层只需要Core就能说话。设计要点对照 MessageBus.h 头文件注释逐一核实主题即类型std::type_index作为订阅表的键API 中没有任何字符串标识符。发布者构造一个不可变消息所有订阅者收到指向同一对象的指针——两个库之间的耦合是一个结构体而非一个类。内存而非拷贝publishT(args…)构造一个std::shared_ptrconst T每个处理器收到const std::shared_ptrconst T指向同一对象。跨线程排队投递捕获指针对象比所有投递活得更久。接收者亲和性subscribeT(QObject* receiver, handler, Qt::ConnectionType)——AutoConnection下发布者与接收者同线程则直接投递否则经QMetaObject::invokeMethod(receiver, …, Qt::QueuedConnection)BlockingQueuedConnection被拒绝因为发布者绝不能等待订阅者。订阅表在互斥锁下拷贝处理器在锁外运行处理器可以再发布。SS_ASSERT(receiver ! nullptr, …)与SS_ASSERT(handler ! nullptr, …)在 SSAssert.h 的断言体系下把关。保留状态publishStateT同时存储最新指针latestT()返回它可空新订阅者若要求replayLatest会在订阅时收到保留消息。这就是共享内存区一个状态主题是一个不可变对象任何库都可以按指针读取。生命周期Subscription析构即退订RAIImove-only见 Subscription.h——SubscriberId在整个总线生命周期内唯一且不复用句柄超龄也不会误取消后来的订阅总线在接收者发出destroyed时也会丢弃其订阅从处理器内部退订是安全的。所有权由组合根ModuleManager构造作为SessionContext槽与九个模块并列暴露为MessageBus::instance()——这是唯一一个有存在理由的全局。单例普查会显示触及从模块类迁移到MessageBus这个迁移就是后续 spec 的度量。不在热路径上每次publish分配一次并拷贝订阅者向量帧/块保持池化 SPSC 通道spec 0055。code-verify.py把热路径白名单文件中的MessageBus引用视为错误。初始词汇表与迁移模式初始词汇Messages.h派生自当晚的单例触及普查连接状态、项目加载/修改、通知产生、仪表板结构变化、录制会话边界、设置变化。每个都是带sourceId/id 与 Qt-Core 值类型的值结构体跨层使用的枚举在第一个消费者迁移时移入Core。头文件中的评审清单spec 0077对任何改动都适用字段只能是 Core 或 Qt Core 值类型字段增删改是线缆断点所有读取方须同一变更内更新保留主题承载事实、请求主题承载问询且绝不带回复槽每个主题恰好一个库发布、发布者绝不指名订阅者任何主题不得以帧/块速率运行。当前 Messages.h 已实现远超计划初始列表的词汇ConnectionStateChanged、NotificationRaised/NotificationPosted含kSeverityInfo/Warning/Critical常量、DashboardStructureChanged/DashboardUpdated/DashboardDataReset、MirrorAttachedChanged、LicenseStateChanged、OperationModeChanged、FrameConfigChanged、ReplayPlayerStateChanged、AudioCaptureFormat、WidgetExtensionEntry/WidgetExtensionCatalog、DashboardViewState系列、DisconnectRequested、DeviceOpenAttempted、ModbusRegisterGroupsLoaded、请求-回复对LoadGeneratedProjectRequested/GeneratedProjectLoadFinished与SourceSettingsCaptureRequested/SourceConnectionSettingsCaptured、ProjectStructureSnapshot含Change枚举与generation计数等并附allocateRequestId()分配进程级请求 id。单例触及 → 总线迁移表每种触及模式的转换范式触及类型现状总线形态读外部状态X::instance().isConnected()拉取保留状态主题latestT()或订阅对外部模块下命令X::instance().connectDevice()直接调用所有者订阅的请求消息T结果作为后续状态主题connect(X::instance(), X::sig, …)单例上的 Qt 信号subscribeTT为信号的载荷结构体后续 spec 的执行顺序每条边一次、编译通过Pipeline→Ui通知、翻译器、Devices→Pipeline帧摄取已是绑定指针spec 0075剩下的是状态、Ui→Devices连接状态、Api→Ui、Storage→Ui然后是App边SerialStudio::activated()变成保留的许可主题AppState变成状态主题。Stage 2 特有风险归档成员被丢弃——只有副作用初始化器的静态归档对象不会被链接。审计app/src中无Q_CONSTRUCTOR_FUNCTION/Q_COREAPP_STARTUP_FUNCTION/静态注册器只有留在main.cpp的Q_INIT_RESOURCEQML 类型在组合根显式注册其对象被引用。遗漏可执行文件级 define——读取SS_INAPP_TESTS的文件若在不带该 define 的库中编译会静默丢失功能。审计列出每个 define 及其读取者。相对 include 歧义——两个根持有同一相对路径。移动后不可能发生没有复制任何文件并由include-ambiguous检查。双重 moc——.h/.cpp对被拆到不同目标。由pair-split检查。链接顺序——环已声明MSVC 与 ld64 不在乎GNU ld/lld 得到归档重复。结语一套可复制的架构治理方法Serial Studio 的这次模块化改造给出了一条从能跑的巨构到分层清晰的库集合的完整路径以构建顺序而非规范实现隔离 → 用棘轮门禁把依赖方向钉死 → 用类型化消息总线取代跨层单例 → 逐边把棘轮翻转为严格。对任何中大型 Qt/C 项目这套组合拳中最可迁移的三样东西是先让隔离成为构建图的性质——add_subdirectory的顺序、include 根的设计比任何代码评审都更可靠把规范变成脚本——scripts/layer-verify.py 的九项检查把不许向上 include不许重复 mocCMake 条目必须真实存在变成 CI 中每次必跑的机械门禁状态走总线、命令走消息——core/Core/Bus/Messages.h 展示了一套以类型为主题、以shared_ptrconst T为投递单位、保留状态可按指针共享的消息总线样板。读者若想深入可以按此顺序阅读仓库设计文档 plan.md → 分层总览 core/CMakeLists.txt → 两个库的目标定义 core/Core/CMakeLists.txt 与 core/Protocols/CMakeLists.txt → 门禁 scripts/layer-verify.py → 总线实现 core/Core/Bus/。【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表