CppMicroServices 核心概念:动态服务注册与模块化系统

CppMicroServices 核心概念:动态服务注册与模块化系统

CppMicroServices README 对自身的定位:

"provides a dynamic service registry and module system "

本文基于 Modules/CppMicroServices/core/src/ 源码解释这两个概念的具体含义。


第一部分 动态服务注册(Dynamic Service Registry)

"动态"是相对"静态绑定"而言的,具体含义可拆成四点。

1. 运行期注册,而非编译期绑定

静态方式(传统 C++):

cpp 复制代码
// 编译期就固定了用哪个实现------换实现要改代码、重新编译
MyAlgorithmImpl algo;
consumer.use(&algo);

动态方式(微服务):

cpp 复制代码
// 提供方:运行期某个时刻才把实现"发布"出去
ctx->RegisterService<IAlgorithm>(new MyAlgorithmImpl);

// 消费方:运行期按接口名查询,编译期完全不知道实现是谁
auto ref = ctx->GetServiceReference<IAlgorithm>();
IAlgorithm* algo = ctx->GetService(ref);

消费方二进制里没有任何对实现类的引用------链接时不需要实现库存在。

2. 服务可以在任意时刻出现和消失

这是"动态"最核心的含义。服务的生命周期不是"程序启动时全部就绪、

退出时全部销毁",而是:

  • 一个 .so 被加载 → 它的服务这时才 注册进来(US_INITIALIZE_MODULE 静态初始化触发)
  • 一个 .so 被卸载 → 它的服务自动 注销(ModulePrivate::RemoveModuleResources
  • 同一接口的服务可以中途被替换、增加、移除

所以消费方必须面对的现实是:"我要的服务现在可能有、可能没有、待会儿可能变"

这正是 ServiceTracker 存在的原因------它不是一次性查询,而是持续跟踪:

cpp 复制代码
// AddingService / RemovedService 回调应对服务的动态来去
// QmitkAbstractView.cpp: L146 实际用例
ctkServiceTracker<mitk::IDataStorageService*> m_DataStorageServiceTracker;

3. 查询是按"接口名字符串 + 属性过滤"的晚绑定

注册表内部就是一张接口名到实现列表的映射(usServiceRegistry_p.h: L63-80):

cpp 复制代码
// 接口名(字符串) → 按 ranking 排序的实现列表
MapClassServices classServices;

查找发生在调用那一刻Get 返回当前 ranking 最高者),而不是链接那一刻。

配合 LDAP 过滤器还能按属性筛选:

cpp 复制代码
GetServiceReferences("IAlgorithm", "(type=fast)");

同一接口多个实现共存、高 ranking 者胜出、随时可被更高 ranking 的新实现顶替

------这些都是静态绑定做不到的。

4. 事件驱动的感知

每次注册/注销都会广播 ServiceEvent(REGISTERED / UNREGISTERING),

监听者实时收到通知(usServiceRegistry.cpp: L128-133)。

系统各部分对"服务拓扑的变化"是被动感知、自动适应的,不需要轮询或重启。

在 MITK 中的实际意义

以文件读取为例:MITK Core 定义 IFileReader 接口;DICOM 模块加载时注册

DICOM 读取器,NRRD 模块加载时注册 NRRD 读取器。打开文件时 Core 按 MIME type

查注册表选用合适的 reader------Core 编译时完全不知道世界上有哪些格式

用户装一个新格式插件,不用改一行 Core 代码,读取能力就"长"出来了。

一句话总结

动态服务注册 = 把"接口→实现"的绑定从编译期/链接期推迟到运行期,
并允许这个绑定关系在程序运行过程中随模块加载卸载而随时变化。


第二部分 模块化系统(Module System)

模块化系统指的是:把动态库从"一堆被动的代码集合"升级为

有身份、有元数据、有生命周期、有资源、可被管理的一等公民

对照源码,具体含义可拆成五点。

1. 模块是有"身份"的实体,而非匿名的 .so

普通 C++ 程序里,动态库加载后就"融化"在进程里------没有名字、没有边界、

无法枚举。而 CppMicroServices 给每个库一个运行期身份:

cpp 复制代码
// usModuleRegistry.cpp: L39-63 ------ 进程级模块注册表
typedef US_UNORDERED_MAP_TYPE<std::string, Module*> ModuleMap;   // name → Module*
US_GLOBAL_STATIC_WITH_DELETER(ModuleMap, modules, ModuleDeleter)

每个模块有唯一自增 id、名字、磁盘路径(ModuleInfo),可以按名查询、全量枚举:

cpp 复制代码
Module* m = ModuleRegistry::GetModule("MitkCore");       // 按名找模块
std::vector<Module*> all = ModuleRegistry::GetModules(); // 枚举所有模块

2. 模块有显式的生命周期状态机

普通 .so 只有"加载/未加载"两态且不可感知。模块系统给出完整生命周期,

且每次状态迁移都广播事件(usModule.cpp: L119-171):

复制代码
注册 → LOADING → (Activator::Load) → LOADED → UNLOADING → (Activator::Unload) → UNLOADED
  • Activator 钩子 :模块加载/卸载时有确定的回调时机
    ModuleActivator::Load/Unload),初始化逻辑有了落脚点,
    不再依赖脆弱的全局对象构造顺序
  • ModuleEvent:其他模块可监听"谁被加载了/卸载了"并做出反应

3. 每个模块有自己的"视角"------ModuleContext

ModuleContextusModuleContext.cpp)是模块与框架交互的专属句柄

------注册服务、查服务、挂监听器都通过它。关键在于框架记得每笔操作是哪个模块做的

cpp 复制代码
// usModulePrivate.cpp: L118-146 ------ 正因为记账到模块头上,卸载时才能自动清理
coreCtx->services.GetRegisteredByModule(this, srs);  // 这个模块注册过什么
coreCtx->services.GetUsedByModule(q, srs);           // 这个模块用过什么

这就是"模块化"与"全局大杂烩"的本质区别:资源归属清晰,卸载即自动回收

不会留下悬空的服务和监听器。

4. 模块自带元数据与资源

  • manifest.jsonusModulePrivate.cpp: L53-68):每个模块可内嵌一份清单,
    声明版本、自动加载目录等属性,框架加载时解析
  • 嵌入式资源系统usModuleResource / ModuleResourceContainer):
    文件可以编译进模块二进制,运行期用统一 API 读取------BlueBerry 读 plugin.xml
    布局选择器读 mxnLayout_*.json 预设用的就是这套机制:
cpp 复制代码
// QmitkMultiWidgetLayoutSelectionWidget.cpp: L42 ------ 从模块资源里找预设布局
us::GetModuleContext()->GetModule()->FindResources("/", "mxnLayout_*.json", false);

5. 模块间的耦合被压缩到"服务接口"这一个通道

模块系统与动态服务注册是配套的:模块是服务的提供者和消费者单位

模块间不直接 include 对方的实现、不直接链接对方的内部符号,

只通过服务接口交互。于是:

  • 模块可独立开发、独立编译、独立替换
  • 依赖关系 = "我需要哪些服务接口",而不是"我链接哪些库"

一句话总结

模块化系统 = 让动态库变成可枚举、可寻址、有生命周期钩子、
有元数据和私有资源、按模块记账并自动回收资源的运行期管理单元

------它提供"秩序"(谁在场、谁负责、何时初始化、何时清理),

动态服务注册提供"通信"(模块间怎么解耦地互相使用),

二者合起来才构成 OSGi 式的动态软件栈。


关键源码文件索引

概念 文件(Modules/CppMicroServices/core/src/ 下) 关键行
模块注册表 module/usModuleRegistry.cpp L39-63(ModuleMap);L75(Register)
模块生命周期 module/usModule.cpp L119(Start);L173(Stop)
Activator 接口 ../include/usModuleActivator.h Load/Unload
模块上下文 module/usModuleContext.cpp L78(RegisterService);L98(GetServiceReference)
按模块记账/清理 module/usModulePrivate.cpp L118-146(RemoveModuleResources)
manifest.json 解析 module/usModulePrivate.cpp L53-68
模块资源系统 module/usModuleResource.cpp / usModuleResourceContainer.cpp -
服务注册表 service/usServiceRegistry.cpp L87(RegisterService);L128-133(事件广播)
服务注册表结构 service/usServiceRegistry_p.h L63-80(两张映射表)
LDAP 过滤器 module/usLDAPExpr.cpp -
自动注册宏 ../include/usModuleInitialization.h L57-120(US_INITIALIZE_MODULE)
MITK 实际用例 Plugins/org.mitk.gui.qt.common/src/QmitkAbstractView.cpp L146(ServiceTracker)
MITK 实际用例 Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp L42(FindResources)
相关推荐
微三云 - 廖会灵 (私域系统开发)2 小时前
电商系统从单体到微服务拆分实践:拆什么、怎么拆、拆完后怎么办
微服务·云原生·架构
guslegend6 小时前
领域驱动设计,微服务设计为什么要选择DDD?
微服务·云原生·架构
VortMall8 小时前
全维度打磨细节体验,赋能商城稳定有序运营|VortMall 微服务商城 v1.3.11 版本发布
java·微服务·云原生·架构·商城系统·开源商城·vortmall
beibeix201519 小时前
MITK 工程四大组结构分析
mitk
豆瓣鸡1 天前
Guava RateLimiter 限流实战:从令牌桶原理到 Nacos 动态配置
spring boot·微服务·nacos·guava
Wang's Blog2 天前
Go-Zero项目开发10: 微服务治理中心实现原理分析
开发语言·微服务·golang
xexpertS2 天前
分布式系统过载治理:如何通过较小服务控制请求节奏
微服务·自动化
爱学习的小可爱卢2 天前
SpringCloud——微服务实战:OpenFeign与Nacos服务调用详解
spring·spring cloud·微服务
吴声子夜歌2 天前
MongoDB 4.x——微服务入门
java·mongodb·微服务