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

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全局注册表(mapname, Module*),模块注册/注销的入口ModuleusModule.cpp模块门面类:Init/Start/Stop/Uninit 生命周期ModulePrivateusModulePrivate.cpp实现细节:解析 manifest.json、持有资源容器和 ModuleContextModuleContextusModuleContext.cpp模块与框架交互的句柄:注册/获取服务、挂监听器ModuleActivator(接口)用户自定义的模块启动/停止钩子(Load/Unload)CoreModuleContextusCoreModuleContext.cpp框架核心:持有服务注册表(ServiceRegistry)、事件监听器列表、HooksModuleResource系列usModuleResource*.cpp读取嵌入在二进制里的资源(编译期打包的 zip)二、主要流程阶段 0:编译期准备MITK 的每个模块经由CMake/mitkFunctionCreateModule.cmake做了三件事:usFunctionGenerateModuleInit()生成一个初始化 cpp;以US_MODULE_NAME模块名编译;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(id1 固定是 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-moduleContextnewModuleContext(this-d);// 1. 创建上下文// 2. 用 C 符号查找该库导出的 activator 工厂函数std::string activator_func_us_module_activator_instance_d-info.name;void*activatorHookSymModuleUtils::GetSymbol(d-info,activator_func.c_str());d-coreCtx-listeners.ModuleChanged(ModuleEvent(LOADING,this));// 3. 广播 LOADINGif(activatorHook)d-moduleActivatoractivatorHook();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()),就可以:RegisterServiceInterface(impl)—— 服务写入CoreModuleContext::services(全局服务注册表);GetServiceReferenceInterface()/GetService()—— 查找并使用其他模块的服务;AddServiceListener/AddModuleListener—— 监听服务和模块事件;GetModule()-GetResource(path)—— 读嵌入资源。这就是模块层和服务层的衔接点:模块层负责谁在场,服务层负责谁提供什么能力。阶段 4:Stop / 卸载——对称的清理库卸载 → 静态对象析构 → ModuleRegistry::UnRegister() → Module::Stop() → 广播 UNLOADING → moduleActivator-Unload(context) // 用户清理 → Uninit() → RemoveModuleResources() → 移除该模块挂的所有监听器 → 强制注销该模块注册的所有服务 → 释放该模块还在用的服务引用(UngetService) → delete moduleContext → 广播 UNLOADED值得注意的是RemoveModuleResources()的兜底设计:即使用户在Unload()里忘了注销服务,框架也会强制清理,防止悬空的服务指针。三、几个设计要点全静态、无框架启动步骤:与 OSGi/新版 CppMicroServices 不同,这个版本没有Framework::Start()、也没有显式install——模块随共享库的加载/卸载自动注册/注销,靠的是静态对象构造/析构。IsLoaded()的判据就是moduleContext ! nullptr。静态初始化顺序控制:usModuleRegistry.cpp 末尾用一个StaticInitializationOrder结构体先强制初始化锁、注册表、CoreModuleContext,最后才执行US_INITIALIZE_MODULE注册核心模块自己——避免静态初始化顺序问题。符号即协议:模块与框架之间不靠头文件链接,而是靠两个约定的 C 符号(moduleInfo静态对象 _us_module_activator_instance_name),用dlsym/GetProcAddress在运行时解析,实现了真正的松耦合。重加载支持:Register()里会先按 locationname 查找是否是同一个库的重新加载,是则复用原 Module 对象和 id。自动加载(autoloading):模块 Start 时会扫描自己的 autoload 目录并加载其中的库(如 MITK 的 IO 插件目录),这是 MITK 各类 reader/writer 插件被自动拉起的机制。一句话总结主流程:编译期生成初始化桩 嵌入 manifest/资源 → 库加载时静态对象触发ModuleRegistry::Register→Init解析元数据 →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 (contextnull) (context!null) (contextnull)没有独立的状态枚举变量——状态的判据就是ModuleContext指针是否存在(usModule.cpp):boolModule::IsLoaded()const{returnd-moduleContext!nullptr;}这是一个很典型的实现选择:上下文对象的生存期本身就是生命周期,不需要额外的状态机维护。二、ModuleActivator:用户侧的生命周期钩子usModuleActivator.h定义了用户参与生命周期的唯一接口,只有两个方法:structModuleActivator{virtualvoidLoad(ModuleContext*context)0;// 模块加载时:注册服务、分配全局资源virtualvoidUnload(ModuleContext*context)0;// 模块卸载时:撤销 Load 做的事};框架给出了三条契约(见头文件注释):对称保证:Load()成功返回,就保证同一个实例的Unload()在卸载时被调用;不并发:框架绝不并发调用同一个 activator 对象;及时返回:两个方法都不允许长时间阻塞,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()做两件事:先经过moduleHooks.FilterModuleEventReceivers()——EventHook 机制,允许注册了ModuleEventHook服务的模块在分发前过滤观众(隐藏某些事件);同步遍历回调所有幸存的监听器,单个监听器抛异常只记警告、不中断分发。注意分发是同步的、在调用 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(...) ← 级联拉起子模块 └─ 广播 LOADEDStop 是镜像过程,但多一层防御: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 过滤表达式引擎ServiceEventServiceListenersusServiceListeners.cppREGISTERED/MODIFIED/MODIFIED_ENDMATCH/UNREGISTERING 事件分发ServiceFactory/PrototypeServiceFactory—按消费方定制实例(module 作用域 / prototype 作用域)ServiceHooksusServiceHooks.cppFindHook/EventListenerHook,拦截查找与事件可见性ServiceTrackerusServiceTracker.tpp高层封装:自动跟踪服务的出现/消失服务的身份靠接口字符串 ID:US_DECLARE_SERVICE_INTERFACE给接口类型绑一个全局唯一字符串(us_service_interface_iidT()),注册和查找都以它为 key——这样跨模块比对不依赖 RTTI。二、注册流程(提供方视角)用户代码context-RegisterServiceIMyService(impl, props)走到ServiceRegistry::RegisterService(),依次:识别注册形态:检查InterfaceMap里有没有org.cppmicroservices.factorykey——有则是工厂注册,再dynamic_cast判断是否PrototypeServiceFactory;合成服务属性(CreateServiceProperties):自动注入三个系统属性——objectclass(实现的接口列表)、service.id(全局递增)、service.scope(singleton / module / prototype);写入三个索引(持锁):services:registration → 接口列表;serviceRegistrations:全量列表;classServices:接口名 → 有序的 registration 数组,插入时用lower_bound按排序规则放置——排序规则是service.ranking降序、同 ranking 按service.id升序,这是后面最佳服务选择的基础;广播 REGISTERED 事件:先GetMatchingServiceListeners()筛出 LDAP 过滤器匹配该服务属性的监听器,再同步回调。三、查找流程(消费方视角)context-GetServiceReferenceIMyService()→ServiceRegistry::Get():按接口名从classServices取出有序数组,应用可选的LDAP filter(如((mimetypeimage/dicom)(priority10)));若 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():unregistering标志去重(重复注销静默忽略);从ServiceRegistry三个索引中移除;先广播 UNREGISTERING,再置available false;对所有工厂产出的实例逐 module 回调UngetService清理,清空dependents。顺序很讲究:事件在失效之前发出,监听者在回调里还能正常GetService做收尾。七、ServiceTracker:消费方的正确姿势裸用GetServiceReference 监听器要自己处理竞态(注册在你 AddListener 之前/之后),ServiceTracker(usServiceTracker.tpp,模板实现)把这套封装掉:Open()时先挂监听器再扫一遍现存服务(闭合竞态窗口),此后自动维护当前匹配的服务集合,并通过ServiceTrackerCustomizer的AddingService/ModifiedService/RemovedService三个回调通知使用者。MITK 内部大量用它实现某类服务随插随用。八、主流程串起来提供方模块 Activator::Load() └─ RegisterServiceI(impl, props) └─ ServiceRegistry: 合成属性(id/scope/objectclass) → 三索引插入(按 ranking 有序) └─ 广播 REGISTERED(LDAP 预过滤监听器) 消费方 └─ GetServiceReferenceI(filter) ← classServices 有序数组 LDAP FindHook,尾部最佳 └─ GetService(ref) ← 按 scope 取/造实例,dependents[module] └─ (或 ServiceTracker 自动跟踪) 提供方退出(Unload 或框架兜底) └─ Unregister() → 移出索引 → 广播 UNREGISTERING → availablefalse → 工厂实例回收一句话总结:服务层 “字符串接口 ID 为 key 的有序注册表(ranking 决定最佳) LDAP 属性查询 四种同步事件 三种实例作用域(singleton/module/prototype) 按模块的引用计数”;它与下两层的接缝在于——注册/查找都要经过ModuleContext(模块层),而模块 Stop 时框架用dependents计数和 registration 归属做强制回收(生命周期层)。全文总结:三层如何咬合层回答的问题核心对象关键机制模块层谁在场Module / ModuleRegistry / ModuleContext静态对象自动注册,manifest 嵌入资源生命周期层何时活、谁感知ModuleActivator / ModuleEvent / SharedLibrary两态模型,同步事件广播,autoload 级联服务层谁提供什么能力ServiceRegistry / ServiceReference / ServiceTracker有序注册表 LDAP 查询 作用域 引用计数三层的咬合点:ModuleContext是模块层交给上层的唯一句柄——服务注册/查找、事件监听都从它出发;模块Start()时调用Activator::Load()(生命周期层),用户在其中注册服务(服务层);模块Stop()时框架按 registration 归属和dependents引用计数强制回收该模块的一切服务痕迹——即使用户代码没做清理,系统也不会留下悬空指针。