当前位置: 首页 > news >正文

MITK之CppMicroServices 三层架构解析:模块层、生命周期层、服务层

CppMicroServices 三层架构解析:模块层、生命周期层、服务层

本文基于 MITK(2020 版)内嵌的 CppMicroServices(us::命名空间,老版,核心概念叫 Module,新版 v3 改叫 Bundle)源码整理。
源码位置:Modules/CppMicroServices/core/src/

三层的分工一句话概括:

  • 模块层:每个共享库是谁(身份、元数据、资源);
  • 生命周期层:模块什么时候活、怎么活、谁能感知;
  • 服务层:谁向谁提供什么能力。

第一部分:模块层(Module Layer)

模块层的全部实现在core/src/module/下,它解决的问题是:把每个共享库(DLL/so)变成一个有身份、有生命周期、有元数据和资源的"模块",并接入服务注册表

一、核心类的分工

文件职责
ModuleInfousModuleInfo.cpp模块的静态信息:名字、id、库文件路径(location)、自动加载目录
ModuleRegistryusModuleRegistry.cpp全局注册表(map<name, Module*>),模块注册/注销的入口
ModuleusModule.cpp模块门面类:Init/Start/Stop/Uninit 生命周期
ModulePrivateusModulePrivate.cpp实现细节:解析 manifest.json、持有资源容器和 ModuleContext
ModuleContextusModuleContext.cpp模块与框架交互的句柄:注册/获取服务、挂监听器
ModuleActivator(接口)用户自定义的模块启动/停止钩子(Load/Unload)
CoreModuleContextusCoreModuleContext.cpp框架核心:持有服务注册表(ServiceRegistry)、事件监听器列表、Hooks
ModuleResource系列usModuleResource*.cpp读取嵌入在二进制里的资源(编译期打包的 zip)

二、主要流程

阶段 0:编译期准备

MITK 的每个模块经由CMake/mitkFunctionCreateModule.cmake做了三件事:

  1. usFunctionGenerateModuleInit()生成一个初始化 cpp;
  2. US_MODULE_NAME=<模块名>编译;
  3. usFunctionEmbedResources()把资源(含manifest.json)打成 zip 附加进二进制。

生成的 cpp 展开US_INITIALIZE_MODULE宏(usModuleInitialization.h),它定义了一个文件级静态对象ModuleInitializer_<模块名>——这是整个机制的触发器。

阶段 1:库加载 → 自动注册(无需任何显式调用)

OS 加载 DLL/so → 静态对象 ModuleInitializer_<name> 构造函数运行 → 通过自身符号地址反查库文件路径(GetLibraryPath),填入 ModuleInfo.location → ModuleRegistry::Register(moduleInfo)

Register()(usModuleRegistry.cpp)的逻辑:

  • 分配全局递增的模块 id(id=1 固定是 CppMicroServices 核心模块自己);
  • new Module()并调用Module::Init();
  • 插入全局modules()映射表,然后调用module->Start()

Init()创建ModulePrivate(usModulePrivate.cpp),这一步完成元数据装配:打开嵌入的资源容器 → 找/manifest.json并解析 → 校验版本号 → 把 id/location/name 写入 manifest 属性 → 确定自动加载目录(默认与模块同名)。

阶段 2:Start——模块进入"已加载"状态

Module::Start()(usModule.cpp)是模块层最关键的一段:

d->moduleContext=newModuleContext(this->d);// 1. 创建上下文// 2. 用 C 符号查找该库导出的 activator 工厂函数std::string activator_func="_us_module_activator_instance_"+d->info.name;void*activatorHookSym=ModuleUtils::GetSymbol(d->info,activator_func.c_str());d->coreCtx->listeners.ModuleChanged(ModuleEvent(LOADING,this));// 3. 广播 LOADINGif(activatorHook)d->moduleActivator=activatorHook();d->moduleActivator->Load(d->moduleContext);// 4. 调用用户的 Load()// 5. 自动加载 <autoload_dir> 下的依赖模块(如果开启)// 6. 广播 LOADED

注意第 2 步:ModuleActivator不是必须的——只有当模块用US_EXPORT_MODULE_ACTIVATOR导出了_us_module_activator_instance_<名字>这个 C 符号时才会被调用。用户通常在Load()里向框架注册服务。

阶段 3:运行期——通过 ModuleContext 使用服务层

模块拿到ModuleContext后(代码里随处可见的us::GetModuleContext()),就可以:

  • RegisterService<Interface>(impl)—— 服务写入CoreModuleContext::services(全局服务注册表);
  • GetServiceReference<Interface>()/GetService()—— 查找并使用其他模块的服务;
  • AddServiceListener/AddModuleListener—— 监听服务和模块事件;
  • GetModule()->GetResource(path)—— 读嵌入资源。

这就是模块层和服务层的衔接点:模块层负责"谁在场",服务层负责"谁提供什么能力"

阶段 4:Stop / 卸载——对称的清理

库卸载 → 静态对象析构 → ModuleRegistry::UnRegister() → Module::Stop() → 广播 UNLOADING → moduleActivator->Unload(context) // 用户清理 → Uninit() → RemoveModuleResources() → 移除该模块挂的所有监听器 → 强制注销该模块注册的所有服务 → 释放该模块还在用的服务引用(UngetService) → delete moduleContext → 广播 UNLOADED

值得注意的是RemoveModuleResources()的兜底设计:即使用户在Unload()里忘了注销服务,框架也会强制清理,防止悬空的服务指针。

三、几个设计要点

  1. 全静态、无框架启动步骤:与 OSGi/新版 CppMicroServices 不同,这个版本没有Framework::Start()、也没有显式install——模块随共享库的加载/卸载自动注册/注销,靠的是静态对象构造/析构。IsLoaded()的判据就是moduleContext != nullptr
  2. 静态初始化顺序控制:usModuleRegistry.cpp 末尾用一个StaticInitializationOrder结构体先强制初始化锁、注册表、CoreModuleContext,最后才执行US_INITIALIZE_MODULE注册核心模块自己——避免静态初始化顺序问题。
  3. 符号即协议:模块与框架之间不靠头文件链接,而是靠两个约定的 C 符号(moduleInfo静态对象 +_us_module_activator_instance_<name>),用dlsym/GetProcAddress在运行时解析,实现了真正的松耦合。
  4. 重加载支持:Register()里会先按 location+name 查找是否是同一个库的重新加载,是则复用原 Module 对象和 id。
  5. 自动加载(autoloading):模块 Start 时会扫描自己的 autoload 目录并加载其中的库(如 MITK 的 IO 插件目录),这是 MITK 各类 reader/writer 插件被自动拉起的机制。

一句话总结主流程:编译期生成初始化桩 + 嵌入 manifest/资源 → 库加载时静态对象触发ModuleRegistry::RegisterInit解析元数据 →Start创建 Context、调用 Activator、广播事件 → 运行期通过 Context 使用服务注册表 → 卸载时对称清理并强制回收服务。


第二部分:生命周期层(Lifecycle Layer)

生命周期层由五部分组成:状态模型、ModuleActivator回调、ModuleEvent+ 监听器分发、SharedLibrary动态装载,以及 autoload 级联加载。

一、状态模型:被简化成两态

OSGi 的 Bundle 有 6 个状态(INSTALLED/RESOLVED/STARTING/ACTIVE/STOPPING/UNINSTALLED),而这版 CppMicroServices 把状态模型压缩成了两态 + 两个过渡瞬间:

Start() Stop() UNLOADED ────[LOADING 事件]───→ LOADED ────[UNLOADING 事件]───→ UNLOADED (context==null) (context!=null) (context==null)

没有独立的状态枚举变量——状态的判据就是ModuleContext指针是否存在(usModule.cpp):

boolModule::IsLoaded()const{returnd->moduleContext!=nullptr;}

这是一个很典型的实现选择:上下文对象的生存期本身就是生命周期,不需要额外的状态机维护。

二、ModuleActivator:用户侧的生命周期钩子

usModuleActivator.h定义了用户参与生命周期的唯一接口,只有两个方法:

structModuleActivator{virtualvoidLoad(ModuleContext*context)=0;// 模块加载时:注册服务、分配全局资源virtualvoidUnload(ModuleContext*context)=0;// 模块卸载时:撤销 Load 做的事};

框架给出了三条契约(见头文件注释):

  1. 对称保证:Load()成功返回,就保证同一个实例的Unload()在卸载时被调用;
  2. 不并发:框架绝不并发调用同一个 activator 对象;
  3. 及时返回:两个方法都不允许长时间阻塞,Unload()返回时不得再有本模块启动的活动线程。

实现机制US_EXPORT_MODULE_ACTIVATOR宏:它生成一个 C 导出函数_us_module_activator_instance_<模块名>,内部用函数级静态变量惰性创建单例activator(析构由ScopedPointer在库卸载时兜底)。框架侧在Module::Start()里用GetSymbol()按名字查这个符号——查不到就静默跳过,所以activator 是可选的,纯资源型模块可以完全不写。

三、生命周期事件与监听器分发

事件对象

ModuleEvent(usModuleEvent.cpp)是一个写时共享(SharedData)的轻量值对象,只携带两个信息:类型(LOADING/LOADED/UNLOADING/UNLOADED)和Module*

谁在发、谁在收

发送方是生命周期的两个入口Module::Start()/Stop();接收方管理和分发全部集中在ServiceListeners(挂在CoreModuleContext上):

  • 注册:任何模块通过ModuleContext::AddModuleListener()挂监听器,按"哪个 ModuleContext 注册的"分组存进moduleListenerMap(usServiceListeners.cpp)——这个分组正是为了模块卸载时能一键清除它挂的所有监听器;
  • 分发:ModuleChanged()做两件事:
    1. 先经过moduleHooks.FilterModuleEventReceivers()——EventHook 机制,允许注册了ModuleEventHook服务的模块在分发前"过滤观众"(隐藏某些事件);
    2. 同步遍历回调所有幸存的监听器,单个监听器抛异常只记警告、不中断分发。

注意分发是同步的、在调用 Start/Stop 的线程上执行——没有事件队列,这也是要求Load()/Unload()快速返回的原因之一。

完整时序(以 Start 为例)

Module::Start() (usModule.cpp) ├─ new ModuleContext ← 状态切换点:此后 IsLoaded()==true ├─ 查符号 _us_module_activator_instance_<name> ├─ 广播 LOADING ──→ ModuleHooks 过滤 ──→ 各监听器回调 ├─ activatorHook() 创建单例 → activator->Load(context) │ (Load 抛异常 = 直接向上传播,模块标记为 stopped, │ 框架清除其监听器/服务) ├─ AutoLoadModules(...) ← 级联拉起子模块 └─ 广播 LOADED

Stop 是镜像过程,但多一层防御:Unload()抛异常时仍会执行Uninit()强制清理(注销服务、移除监听器、释放服务引用、删除 context、广播 UNLOADED)——用户代码失败不能让框架处于半死状态

四、SharedLibrary:手动控制生命周期的把手

前面说过模块随库加载"自动"注册,那主动触发生命周期靠什么?答案是SharedLibrary类(usSharedLibrary.cpp),它是对dlopen/dlclose(POSIX)和LoadLibrary/FreeLibrary(Windows)的跨平台薄封装:

us::SharedLibrarylib(path,"MyModule");lib.Load();// dlopen → 静态初始化 → ModuleRegistry::Register → Module::Start...lib.Unload();// FreeLibrary → 静态析构 → UnRegister → Module::Stop

关键点:Load()/Unload()本身不含任何模块逻辑——它只是触发 OS 装载器,真正的生命周期动作由库内静态对象的构造/析构连锁引发(US_INITIALIZE_MODULE机制)。生命周期层和 OS 装载器是绑死的,这也是这版与新版 CppMicroServices(Bundle 可以 install 而不 start)最大的区别。

五、Autoload:级联生命周期

Module::Start()的最后一步是自动加载(usUtils.cppAutoLoadModules):遍历ModuleSettings里配置的 autoload 基路径,扫描其下与本模块同名的子目录(moduleInfo.autoLoadDir,默认=模块名),把里面的每个库用SharedLibrary::Load()拉起来——每个被拉起的库又走一遍完整的 Register→Start 流程,形成级联加载

MITK 靠这个机制实现插件式 IO:比如MitkCore模块启动时,MitkCore/autoload 目录下的各种 reader/writer 模块被自动激活,无需任何显式代码。

六、总结

环节机制关键文件
状态两态,以moduleContext != nullptr为判据usModule.cpp
用户钩子ModuleActivator::Load/Unload,C 符号导出,可选,单例usModuleActivator.h
事件4 种ModuleEvent,同步分发,Hook 可过滤usModuleEvent.cpp、usServiceListeners.cpp、usModuleHooks.cpp
触发源OS 装载器(静态构造/析构)或SharedLibrary::Load/UnloadusSharedLibrary.cpp
级联Start 末尾扫描 autoload 目录拉起子模块usUtils.cpp
兜底Stop 时强制注销服务/监听器,防泄漏usModulePrivate.cppRemoveModuleResources()

一句话:生命周期层 = “OS 装载事件 → Register/UnRegister → Start/Stop → LOADING/LOADED/UNLOADING/UNLOADED 事件广播 → Activator 回调 + autoload 级联”,全程同步执行,以 ModuleContext 的存在为状态,以强制清理为兜底。


第三部分:服务层(Service Layer)

三层里最上面的一层,本质是一个进程内的、带属性查询和事件通知的服务注册表。实现集中在core/src/service/

一、核心组件

组件文件职责
ServiceRegistryusServiceRegistry.cpp全局注册表:三个索引结构 + 注册/查询入口
ServiceRegistrationBaseusServiceRegistrationBase.cpp注册凭据(服务提供方持有),可更新属性、注销
ServiceReferenceBaseusServiceReferenceBase.cpp服务引用(消费方持有),轻量、可比较排序
ServiceProperties/LDAPExprusLDAPExpr.cpp服务属性字典 + LDAP 过滤表达式引擎
ServiceEvent+ServiceListenersusServiceListeners.cppREGISTERED/MODIFIED/MODIFIED_ENDMATCH/UNREGISTERING 事件分发
ServiceFactory/PrototypeServiceFactory按消费方定制实例(module 作用域 / prototype 作用域)
ServiceHooksusServiceHooks.cppFindHook/EventListenerHook,拦截查找与事件可见性
ServiceTrackerusServiceTracker.tpp高层封装:自动跟踪服务的出现/消失

服务的"身份"靠接口字符串 ID:US_DECLARE_SERVICE_INTERFACE给接口类型绑一个全局唯一字符串(us_service_interface_iid<T>()),注册和查找都以它为 key——这样跨模块比对不依赖 RTTI。

二、注册流程(提供方视角)

用户代码context->RegisterService<IMyService>(impl, props)走到ServiceRegistry::RegisterService(),依次:

  1. 识别注册形态:检查InterfaceMap里有没有"org.cppmicroservices.factory"key——有则是工厂注册,再dynamic_cast判断是否PrototypeServiceFactory;
  2. 合成服务属性(CreateServiceProperties):自动注入三个系统属性——objectclass(实现的接口列表)、service.id(全局递增)、service.scope(singleton / module / prototype);
  3. 写入三个索引(持锁):
    • services:registration → 接口列表;
    • serviceRegistrations:全量列表;
    • classServices:接口名 → 有序的 registration 数组,插入时用lower_bound按排序规则放置——排序规则是service.ranking降序、同 ranking 按service.id升序,这是后面"最佳服务"选择的基础;
  4. 广播 REGISTERED 事件:先GetMatchingServiceListeners()筛出 LDAP 过滤器匹配该服务属性的监听器,再同步回调。

三、查找流程(消费方视角)

context->GetServiceReference<IMyService>()ServiceRegistry::Get():

  • 按接口名从classServices取出有序数组,应用可选的LDAP filter(如"(&(mimetype=image/dicom)(priority>=10))");
  • 若 filter 没给接口名,LDAPExpr::GetMatchedObjectClasses()会尝试从表达式里反推出涉及的接口,避免全表扫描;
  • 结果经过ServiceHooks(FindHook)过滤——允许安装了钩子的模块对特定消费者隐藏服务;
  • 单个查找返回srs.back()——由于数组按 ranking/id 有序,尾部就是"最佳"服务(ranking 最高者)。

四、获取实例流程:三种作用域 + 引用计数

拿到引用后GetService()才真正取实例,核心在 usServiceReferenceBasePrivate.cpp:

GetService(module) ├─ registration->available? 否 → nullptr ├─ 是工厂注册? │ ├─ singleton:直接返回注册时的裸指针 │ ├─ module 作用域:该 module 首次获取 → factory->GetService(module) 造实例, │ │ 存入 moduleServiceInstance[module];再次获取 → 返回缓存 │ └─ prototype:每次 GetService 都造新实例(prototypeServiceInstances 按 module 记 list) └─ dependents[module]++ ← 按消费模块计数

两个防御点值得注意:工厂返回空会被拒绝;工厂返回的对象必须覆盖注册时声明的全部接口,否则整个结果作废并告警——防止工厂"货不对板"。

UngetService()反向递减dependents,归零时对工厂实例回调factory->UngetService()销毁。这个计数是生命周期层兜底清理的依据:RemoveModuleResources()就是遍历dependents强制释放垂死模块占用的服务。

五、事件与监听:LDAP 预过滤

消费方通常不轮询,而是AddServiceListener(callback, "(objectclass=...)")。实现上每个监听器条目(ServiceListenerEntry)携带预编译的 LDAP 表达式,ServiceListeners还维护了按objectclass/service.id监听器缓存索引——事件到来时先用属性做哈希命中,只对候选者求值 LDAP,而不是每次遍历所有监听器。

四种事件的语义:

  • REGISTERED:新服务上线;
  • MODIFIED:属性变了且匹配你的 filter;
  • MODIFIED_ENDMATCH:属性变了且不再匹配你的 filter(SetProperties里对比变更前后两组监听器发出);
  • UNREGISTERING:注销广播,给监听者最后机会释放引用。

六、注销流程

ServiceRegistrationBase::Unregister():

  1. unregistering标志去重(重复注销静默忽略);
  2. ServiceRegistry三个索引中移除;
  3. 先广播 UNREGISTERING,再置available = false;
  4. 对所有工厂产出的实例逐 module 回调UngetService清理,清空dependents

顺序很讲究:事件在失效之前发出,监听者在回调里还能正常GetService做收尾。

七、ServiceTracker:消费方的正确姿势

裸用GetServiceReference+ 监听器要自己处理竞态(注册在你 AddListener 之前/之后),ServiceTracker(usServiceTracker.tpp,模板实现)把这套封装掉:Open()时先挂监听器扫一遍现存服务(闭合竞态窗口),此后自动维护"当前匹配的服务集合",并通过ServiceTrackerCustomizerAddingService/ModifiedService/RemovedService三个回调通知使用者。MITK 内部大量用它实现"某类服务随插随用"。

八、主流程串起来

提供方模块 Activator::Load() └─ RegisterService<I>(impl, props) └─ ServiceRegistry: 合成属性(id/scope/objectclass) → 三索引插入(按 ranking 有序) └─ 广播 REGISTERED(LDAP 预过滤监听器) 消费方 └─ GetServiceReference<I>(filter) ← classServices 有序数组 + LDAP + FindHook,尾部=最佳 └─ GetService(ref) ← 按 scope 取/造实例,dependents[module]++ └─ (或 ServiceTracker 自动跟踪) 提供方退出(Unload 或框架兜底) └─ Unregister() → 移出索引 → 广播 UNREGISTERING → available=false → 工厂实例回收

一句话总结:服务层 = “字符串接口 ID 为 key 的有序注册表(ranking 决定最佳)+ LDAP 属性查询 + 四种同步事件 + 三种实例作用域(singleton/module/prototype)+ 按模块的引用计数”;它与下两层的接缝在于——注册/查找都要经过ModuleContext(模块层),而模块 Stop 时框架用dependents计数和 registration 归属做强制回收(生命周期层)。


全文总结:三层如何咬合

回答的问题核心对象关键机制
模块层谁在场Module / ModuleRegistry / ModuleContext静态对象自动注册,manifest + 嵌入资源
生命周期层何时活、谁感知ModuleActivator / ModuleEvent / SharedLibrary两态模型,同步事件广播,autoload 级联
服务层谁提供什么能力ServiceRegistry / ServiceReference / ServiceTracker有序注册表 + LDAP 查询 + 作用域 + 引用计数

三层的咬合点:

  1. ModuleContext是模块层交给上层的唯一句柄——服务注册/查找、事件监听都从它出发;
  2. 模块Start()时调用Activator::Load()(生命周期层),用户在其中注册服务(服务层);
  3. 模块Stop()时框架按 registration 归属和dependents引用计数强制回收该模块的一切服务痕迹——即使用户代码没做清理,系统也不会留下悬空指针。
http://www.jsqmd.com/news/1263994/

相关文章:

  • remix-i18next TypeScript类型安全实践:确保翻译键与类型定义同步
  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 数字身份克隆技术:Second Me开源项目解析与应用
  • SmartSentinel调试日志:实时追踪JSON解析错误的终极工具
  • 技术焦虑下的业务聚焦:构建可持续的技术竞争力
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • 仅限本周开放|GMAT AI备考效能评估工具(含ETS官方题库行为轨迹比对模块),免费生成专属「提分热力图」与瓶颈突破路线图
  • 权威媒体一致认可:欧米到家 | 郑州家用空调维修 | 中央空调维修 | 获评家电服务行业靠谱品牌(凤凰网、中华网等多家媒体推荐) - 欧米到家
  • jsonpath-ng常见问题解答:从安装错误到复杂查询调试
  • Agency-Agents:分布式AI代理协作框架解析与实践
  • MySQL与TiDB无缝切换:YaSQL多数据库支持的实现原理与最佳实践
  • 同样转大模型,运维背景的优势和短板分别是什么?
  • AI智能体网络在B2B电商的应用:技术架构与实施指南
  • Claude Code后台任务管理:/tasks命令详解与并行开发实战
  • JetBrains紧急修复18个高危漏洞:两个CVSS满分漏洞潜伏在远程开发功能中,开发者工作站已成供应链攻击新入口
  • GB28181标准下智能视频监控系统的优化与实践
  • AI工具优化学术论文写作全流程指南
  • BitNet三值量化技术解析与边缘计算实践
  • GPMC时序配置深度解析:从理论到实践,确保外部存储器稳定通信
  • 2026年嘉兴合同纠纷律师怎么选?张锦等5位本土专业律师推荐 - 本地品牌推荐
  • WPF治具上位机软件模板分享
  • OpenClaw隐私行为与中国法下的智能合规闭环
  • 科颜氏洗面奶代加工揭秘:源头工厂工艺公差与验货利润全拆解
  • Python 面向对象进阶与实战:从继承到学生管理系统
  • Visual Studio 2022 C++开发环境搭建与实战配置指南
  • 神经网络与人类认知的相似性及AI应用
  • 小程序毕设选题推荐:基于Django的校园车辆停放智能服务小程序设计【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 为什么选择DynamicJSON?Swift开发者必知的7大核心优势
  • YOLOv11水果识别系统:从数据集构建到PyQt5部署
  • 创世战车WORM流派复兴:雷神炮战术配装与实战解析