MITK中微服务三套注册表数据结构分析
MITK 三套注册表数据结构分析第一部分BlueBerry 扩展注册表ExtensionRegistry / RegistryObjectManager第二部分CppMicroServices 注册表ModuleRegistry / ServiceRegistry第三部分CTK 中的注册表ctkPlugins / ctkServices第四部分血缘关系 —— Knopflerfish 与 Equinox附CppMicroServices 版本信息第一部分 BlueBerry 扩展注册表的数据结构源码Plugins/org.blueberry.core.runtime/src/internal/核心是RegistryObjectManager。一、总体设计int ID 扁平对象图 多索引注册表不是一棵指针连接的对象树而是所有对象压平存储以 int objectId 为主键父子关系用 int 数组表达再配多张字符串索引表加速查询。┌─────────────────────────────────────────┐ │ RegistryObjectManager │ 字符串索引 │ extensionPoints: QHashQString,int │──┐ 名字→ID │ namespacesIndex: KeyedHashSet │ │ 查到 id │ contributors: QHashQString,Contrib │ │ │ orphanExtensions: QHashQString,QListint│ ├─────────────────────────────────────────┤ │ 主存储 │ cache: RegistryObjectReferenceMap │◄─┘ ID→对象 │ (int → RegistryObject, 可软引用) │ │ heldObjects: KeyedHashSet (强引用锚) │ └─────────────────────────────────────────┘二、对象本体RegistryObjectberryRegistryObject.h: L26三类对象ExtensionPoint / Extension / ConfigurationElement共享同一基类classRegistryObject:publicKeyedElement{uint objectId;// 主键nextId 自增分配QListintchildren;// ★ 子对象只存 ID 数组不存指针intextraDataOffset;// 位打包bit31无额外数据, bit30持久化标志,// bit0-29缓存文件偏移(≤1GB)};两个关键决策children是QListint——树结构完全靠 ID 间接引用。好处对象可独立序列化到磁盘缓存、可懒加载、删除时无悬空指针。extraDataOffset位打包——一个 int 同时编码持久化标志 缓存文件偏移。ConfigurationElement 的属性存法berryConfigurationElement.h: L36-40极致紧凑// 格式: [p1, v1, p2, v2, ..., configurationElementValue]// 偶数长度 没有元素文本值属性名/值交替排列QListQStringpropertiesAndValue;// 不用 QHash一个扁平字符串数组每元素通常只有 2~4 个属性线性扫比哈希省内存。三、主存储RegistryObjectReferenceMapID → 对象berryRegistryObjectManager.h: L208RegistryObjectReferenceMap*cache;// key: object id, value: 对象KeyedHashSet heldObjects;// 必须常驻内存对象的强引用RegistryObjectReferenceMap支持HARD/SOFT 两种引用模式berryRegistryObjectReferenceMap.h: L32-37——软引用模式下不常用的对象可被回收需要时从磁盘缓存按extraDataOffset重新加载Load(id, type)。Add(object, hold)的hold参数决定是否放进heldObjects锚定。四、索引表按不同维度加速查询索引类型用途extensionPointsHashtableOfStringAndIntQHashQString,int薄封装扩展点全名 → objectIdnamespacesIndexKeyedHashSet内部QSetKeyedElement元素自带 GetKey命名空间 → 该插件的 extension/extensionPoint 集合contributorsQHashQString, RegistryContributor贡献者 ID → 贡献者哪个插件orphanExtensionsQHashQString, QListint孤儿表目标扩展点未注册的 extension 暂存等扩展点出现再认领newContributions/formerContributionsKeyedHashSet本次会话新增贡献 vs 上次会话缓存贡献懒加载五、对外暴露Handle 层安全间接引用消费者从来拿不到RegistryObject裸指针拿到的是HandleberryHandle.h: L31classHandle:publicvirtualObject{constIObjectManager*constobjectManager;intid;// Handle 只持有 IDvirtualRegistryObject*GetObject();// 每次访问按 ID 现查};IConfigurationElement/IExtension/IExtensionPoint的实现全是ID 管理器指针的轻量句柄。好处插件卸载、对象移除后Handle 访问得到受控失败而非野指针崩溃Handle 可随意复制代价只是一个 int。六、线程安全与懒加载所有公开方法加QMutex mutexL105内部用_unlocked后缀版本避免重入死锁多个xxxLoaded布尔标志orphanExtensionsLoaded、contributorsLoaded、namespacesIndexLoaded实现分区懒加载——哪部分被查询才加载哪部分isDirty/fromCache标志支撑注册表磁盘缓存启动时间戳一致则直接映射缓存文件跳过全部 plugin.xml 解析七、查询路径示例GetConfigurationElementsFor(org.blueberry.ui.views) → extensionPoints 索引查到扩展点 objectId → cache.Get(id) 取 ExtensionPoint 对象可能触发磁盘 Load → GetRawChildren() 拿到 extension 的 int ID 数组 → 逐个 GetObjects(ids, EXTENSION) → 再取各自 childrenConfigurationElement ID → 包装成 ConfigurationElementHandle 列表返回小结设计点实现主键化一切对象 int objectIdnextId 自增树 ID 数组children: QListint无指针互连属性 扁平数组[p1,v1,p2,v2,...,value]交替存放内存可伸缩ReferenceMap 软引用 heldObjects 强引用锚 磁盘偏移量重加载多维索引名字/命名空间/贡献者/孤儿四套哈希索引安全暴露HandleID管理器而非裸指针并发单 QMutex _unlocked内部版本启动加速位打包偏移 分区懒加载 时间戳校验缓存这套结构原样照搬 Eclipse Equinox——为数千插件、数万扩展的规模优化启动不解析、内存可回收、查询走索引。第二部分 CppMicroServices 注册表的数据结构与 BlueBerry 完全不同的设计——纯内存、指针直连、按需求即时索引的三层哈希结构。源码Modules/CppMicroServices/core/src/。一、三张注册表总览① ModuleRegistry模块表全局静态 QHash: 模块名 → Module* ② ServiceRegistry服务表挂在 CoreModuleContext 里 三个容器互为视角指向同一批 ServiceRegistrationBase ③ ServiceListeners监听器表 按过滤条件分桶的监听器缓存二、ModuleRegistry —— 模块表module/usModuleRegistry.cpp: L39-73typedefUS_UNORDERED_MAP_TYPEstd::string,Module*ModuleMap;// name → Module*US_GLOBAL_STATIC_WITH_DELETER(ModuleMap,modules,ModuleDeleter)// 进程级静态单例US_GLOBAL_STATIC(Mutex,modulesLock)// 独立互斥锁US_GLOBAL_STATIC(Mutex,countLock)// 保护自增 id 计数一张unordered_mapstring, Module*裸指针直连对象生命周期与 .so 绑定进程退出时由 ModuleDeleter 统一清理。三、ServiceRegistry —— 服务表核心service/usServiceRegistry_p.h: L63-82classServiceRegistry{mutableMutex mutex;// 单锁保护全部三个容器// 视角1: 服务注册对象 → 它声明的接口名列表US_UNORDERED_MAP_TYPEServiceRegistrationBase,std::vectorstd::stringservices;// 视角2: 接口名 → 按 ranking 升序的注册列表★ 查询主索引US_UNORDERED_MAP_TYPEstd::string,std::vectorServiceRegistrationBaseclassServices;// 视角3: 全量平面列表按模块枚举时用std::vectorServiceRegistrationBaseserviceRegistrations;};三个容器存同一批 ServiceRegistrationBase引用计数句柄索引维度不同classServices是查询热路径GetServiceReference(IAlgorithm)一次哈希命中取back()即最高 ranking插入时lower_bound保序usServiceRegistry.cpp: L121-124一个服务注册 N 个接口就在classServices出现 N 次四、服务对象本体ServiceRegistrationBasePrivateusServiceRegistrationBasePrivate.h: L40-115classServiceRegistrationBasePrivate{AtomicInt ref;// 隐式共享的引用计数handle/body 惯用法InterfaceMap service;// ★ 服务本体: std::mapstring接口名, void*实现指针// (usServiceInterface.h: L138)// ---- 使用者记账与 BlueBerry 最大的不同点----ModuleToRefsMap dependents;// QHashModule*,int: 谁在用我 未配对的 GetService 次数ModuleToServiceMap moduleServiceInstance;// 模块作用域工厂: 每模块一个实例ModuleToServicesMap prototypeServiceInstances;// 原型工厂: 每模块多个实例ModulePrivate*module;// 谁注册的我裸指针回连ServiceReferenceBase reference;// 对外发放的引用对象ServicePropertiesImpl properties;// 属性表service.id / objectclass / service.rankingvolatileboolavailable;// 服务是否可获取volatileboolunregistering;// 防递归注销标志Mutex eventLock,propsLock;// 细粒度锁};关键设计InterfaceMap std::mapstd::string, void*——服务本体就是接口名 → 对象指针的 map。C 无反射多接口注册靠模板在编译期把T*转void*存入取出时按接口名转回。dependents使用计数——每次 GetService 1、UngetService -1模块卸载时框架据此知道谁还欠着我的服务没还实现自动清理。handle/body 分离——对外的ServiceRegistrationBase/ServiceReferenceBase都是指向 Private 的引用计数薄壳服务注销后 Private 仍可存活availablefalse旧引用访问得到受控失败。五、ServiceListeners —— 监听器表service/usServiceListeners_p.h: L43-65带过滤器优化的两级结构classServiceListeners{// 简单过滤器只按 objectclass 匹配→ 缓存桶事件分发 O(1) 定位US_UNORDERED_MAP_TYPEstd::string,std::listServiceListenerEntrycache[2];// 按 objectclass 和 service.id 两个维度各一份// 复杂 LDAP 过滤器 / 无过滤器 → 只能逐个求值std::listServiceListenerEntrycomplicatedListeners;// 模块级监听器ModuleContext → 回调列表US_UNORDERED_MAP_TYPEModuleContext*,std::liststd::pairModuleListener,void*moduleListenerMap;};发 ServiceEvent 时先按事件服务的 objectclass/service.id 命中缓存桶里的简单监听器再对 complicatedListeners 逐个跑 LDAP 表达式——避免每次事件全量遍历。第三部分 CTK 中的注册表CTK 源码位置D:\MITK-superbuild-2023.12\ep\src\CTK\Libs\PluginFramework\CTK 是外部依赖由CMakeExternals/CTK.cmake从 github.com/MITK/CTK 拉取结论先行CTK 有两张注册表插件表 服务表但没有 ExtensionRegistry。BlueBerry ExtensionRegistry 不是依赖 CTK 实现的——它的解析与存储是BlueBerry 自有代码CTK 只提供输入源和触发时机详见下文第三节。一、CTK 的两张注册表1. ctkPlugins —— 插件注册表ctkPlugins_p.h: L45-63classctkPlugins{QHashQString,QSharedPointerctkPluginplugins;// location(URL) → 插件mutableQReadWriteLock pluginsLock;// 读写锁比 us:: 的单 Mutex 更细};2. ctkServices —— 服务注册表ctkServices_p.h: L40-71classctkServices{QHashctkServiceRegistration,QStringListservices;// 注册 → 接口名列表QHashQString,QListctkServiceRegistrationclassServices;// 接口名 → 按rank排序的注册列表};这与第二部分 CppMicroServices 的ServiceRegistry几乎逐字段对应services/classServices连名字都一样——因为两者同源见第四部分只是 CTK 用 Qt 容器 QSharedPointerus:: 用 STL 自制引用计数。二、CTK 独有的两个特性1. SQLite 持久化ctkPluginStorageSQLus:: 和 BlueBerry 都没有的能力// ctkPluginStorageSQL.cpp: L38-39#definePLUGINS_TABLEPlugins// 插件元数据表#definePLUGIN_RESOURCES_TABLEPluginResources// ★ 插件资源含 plugin.xml存进 SQLite!CTK 把插件安装状态、以及插件内嵌资源整个存入 SQLite 数据库。plugin-getResource(plugin.xml)实际是从这个数据库读出来的。2. Qt 信号槽做服务事件ctkServiceSlotEntry监听器不是回调接口而是 Qt slot配 LDAP 过滤器ctkLDAPExpr同样来自 Knopflerfish。三、修正“BlueBerry ExtensionRegistry 依赖 CTK 实现”准确的关系是分层协作而非实现依赖环节谁做的plugin.xml字节从哪来CTKplugin-getResource()从 SQLite 资源表取出何时触发解析CTK插件 RESOLVED 事件 →CTKPluginListener::PluginChangedExtensionRegistry注册为服务CTKcontext-registerServiceIExtensionRegistry()XML解析BlueBerry 自己ExtensionsParserSAX 状态机存储结构BlueBerry 自己RegistryObjectManager第一部分详析查询/Handle APIBlueBerry 自己所以CTK 是 ExtensionRegistry 的宿主与快递员管插件生命周期、送来 plugin.xml 字节但注册表本身的解析、存储、查询全部是 BlueBerry从 Eclipse Equinox 移植的自有代码。CTK 自己压根没有扩展点概念Libs/下搜不到 ExtensionRegistry只有无关的 ctkExtensionFactory——那是 Qt Designer 插件工厂。第四部分 血缘关系Knopflerfish 与 EquinoxKnopflerfish 是什么Knopflerfish是一个开源的 Java OSGi 框架实现——理解它是什么就能理解为什么 CTK 和 CppMicroServices 的代码长得几乎一样。由瑞典公司Makewave前身 Gatespace Telematics开发维护2003 年开源BSD 风格许可证是 OSGi 规范最早、最著名的三大实现之一OSGi 实现维护方著名衍生物EquinoxEclipse 基金会Eclipse IDE 的内核 →BlueBerry移植自它FelixApache众多 Java 服务器KnopflerfishMakewave→CTK PluginFramework和CppMicroServices移植自它OSGi 是什么一句话背景OSGi Java 世界的动态模块化规范程序由多个bundle模块组成bundle 可在运行期安装/启动/停止/卸载bundle 之间通过服务注册表以接口解耦通信。动态服务注册 模块化系统这两个概念就是 OSGi 规范的核心内容。与 MITK 的血缘关系KnopflerfishJava, OSGi 实现 │ ├─ CTK PluginFramework把 Java 代码手工翻译成 Qt/C │ ctkServices、ctkLDAPExpr、ctkServiceSlotEntry ... │ └─ CppMicroServices翻译成纯 STL C usServiceRegistry、usLDAPExpr、usServiceListenerEntry ...翻译痕迹在源码里非常明显类名逐一对应Java 的Services→ctkServices/us::ServiceRegistryLDAPExpr→ctkLDAPExpr/usLDAPExpr数据结构逐字段对应services/classServices两张表的名字在三份代码里一模一样连注释和算法如 LDAP 过滤器求值、service ranking 排序都是同一套而BlueBerry 走的是另一条线它移植的是EquinoxEclipse 的 OSGi 实现里的扩展注册表和 Workbench所以ExtensionRegistry/RegistryObjectManager的设计int ID 扁平图、磁盘缓存、孤儿表与 Knopflerfish 系毫无相似之处——那是 Eclipse 的家传手艺。MITK 里OSGi 味的代码其实有两个不同的 Java 祖先两套微服务机制CTK 服务层、CppMicroServices是 Knopflerfish 的 C 后代BlueBerry 的扩展点机制则来自 Eclipse Equinox。第五部分 三套注册表全景对照CTK ctkServices/ctkPluginsCppMicroServices ServiceRegistryBlueBerry ExtensionRegistry血统Knopflerfish OSGi (Java→Qt)Knopflerfish OSGi (Java→STL)Eclipse Equinox (Java→Qt)管什么插件 服务活对象模块 服务活对象扩展声明静态元数据主键接口名字符串 / location接口名字符串 / 模块名int objectId核心结构QHash 双表unordered_map 双表int ID 扁平图 多索引对象互连QSharedPointer裸指针/引用计数句柄int ID 数组间接引用持久化SQLite含资源无自制二进制缓存 偏移量记账重点谁在用dependents谁在用dependents谁贡献的contributor 归属事件Qt 信号槽 LDAP回调 LDAPIRegistryEventListener锁QReadWriteLock单 Mutex单 QMutex规模假设数十插件数百服务查询要快数万扩展启动要快典型查询接口名 → 服务接口名 → 最高 ranking 实例扩展点 ID → 配置元素树本质差异一句话Service 类注册表管运行中的对象所以要引用计数、使用记账、可用性标志ExtensionRegistry 管静态的声明所以要 ID 化、可序列化、懒加载——分别对应 OSGi 的 Service Layer 和 Eclipse 的Extension Registry。三者在 MITK 进程里同时运行CTK 管 Plugins 层生命周期与框架服务us:: 管 Modules 层算法服务BlueBerry ExtensionRegistry管 UI 扩展声明——各司其职。附 CppMicroServices 版本信息版本2.99.0内嵌定制版。来源Modules/CppMicroServices/CMakeLists.txtset(${PROJECT_NAME}_MAJOR_VERSION 2) set(${PROJECT_NAME}_MINOR_VERSION 99) set(${PROJECT_NAME}_PATCH_VERSION 0)2.99.0 是2.x 向 3.0 过渡的开发版本号99 是惯用的预发布标记。不是上游正式 release而是 MITKfork 进源码树内嵌维护的版本——所以它放在Modules/CppMicroServices/而不是CMakeExternals/作为外部依赖。API 特征印证仍使用2.x 时代术语——us::命名空间、Module/ModuleContext/US_INITIALIZE_MODULE基于 OSGi Core Release 5 规范。上游独立项目github.com/CppMicroServices/CppMicroServices3.x 之后改成cppmicroservices::命名空间和Bundle/BundleContext术语MITK 内嵌版没有跟进由 MITK 团队DKFZ随主仓库维护。关键源码文件索引BlueBerryPlugins/org.blueberry.core.runtime/src/internal/下内容文件关键行对象管理器berryRegistryObjectManager.hL203-239全部私有字段对象基类berryRegistryObject.hL26-100配置元素属性berryConfigurationElement.hL36-49软/硬引用 MapberryRegistryObjectReferenceMap.hL32-70字符串→int 表berryHashtableOfStringAndInt.hL20KeyedHashSetberryKeyedHashSet.h/berryKeyedElement.h-Handle 基类berryHandle.hL31-56CppMicroServicesModules/CppMicroServices/core/src/下内容文件关键行模块表module/usModuleRegistry.cppL39-73服务表service/usServiceRegistry_p.hL63-82服务注册私有体service/usServiceRegistrationBasePrivate.hL40-115InterfaceMap 定义../include/usServiceInterface.hL138监听器表service/usServiceListeners_p.hL43-65版本号../CMakeLists.txtMAJOR/MINOR/PATCH_VERSIONCTKep/src/CTK/Libs/PluginFramework/下内容文件关键行CTK 外部依赖声明D:/MITK/CMakeExternals/CTK.cmakeL57-58GIT_REPOSITORY / GIT_TAG插件注册表ctkPlugins_p.hL45-63服务注册表ctkServices_p.hL40-71SQLite 持久化ctkPluginStorageSQL_p.h/.cppcpp L38-39表名服务事件槽ctkServiceSlotEntry_p.hL45-75LDAP 过滤器ctkLDAPExpr_p.h-