class_loader 源码详细分析 工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros/class_loader 版本:2.2.0 (package.xml),语言 C++14 ,许可证 BSD ,Quality Level 1 。
class_loader 是 ROS 无关 的运行时插件加载库:通过 dlopen/LoadLibrary 打开 .so/.dll,在库加载时自动注册工厂(MetaObject),运行时按类名 new 出插件实例,无需在编译期链接插件头文件 。ROS 2 中 pluginlib 在其之上封装 XML 清单与包名解析;rclcpp_components 则直接用 CLASS_LOADER_REGISTER_CLASS 实现组件节点动态加载。
1. 总体架构 应用 / pluginlib / ComponentManager class_loader::impl 全局状态 插件 .so OS / rcpputils loadLibrary 触发静态初始化 createInstance unloadLibrary ClassLoader / MultiLibraryClassLoader BaseToFactoryMapMap<br/>基类 → 类名 → MetaObject LoadedLibraryVector<br/>已加载 .so 句柄 MetaObjectGraveyard<br/>卸载暂存工厂 CLASS_LOADER_REGISTER_CLASS<br/>静态 ProxyExec 构造 MetaObject<Derived, Base> rcpputils::SharedLibrary<br/>dlopen/dlclose
层次
文件
职责
用户 API
class_loader.hpp
单库 ClassLoader
多库 API
multi_library_class_loader.hpp
管理多个 ClassLoader
核心实现
class_loader_core.hpp/.cpp
全局工厂表、load/unload
工厂
meta_object.hpp/.cpp
MetaObject<C,B> 模板
注册宏
register_macro.hpp
静态初始化注册
底层加载
rcpputils::SharedLibrary
跨平台动态库
源码规模很小:4 个 .cpp、约 6 个头文件、39 个仓库文件。
2. 核心概念 每个可实例化的插件类对应一个 MetaObject<Derived, Base> :
1 2 3 4 B * create() const { return new C; }
AbstractMetaObjectBase :非模板基类,存类名、基类名、关联库路径、所属 ClassLoader 列表
基类索引键为 typeid(Base).name() (Linux 上常为 mangled 名,Windows 上常为字面类名)
用户可见类名为 未 mangled 的字符串 (如 "Dog")
2.2 全局工厂表 1 2 typedef std::map<ClassName, impl::AbstractMetaObjectBase *> FactoryMap; typedef std::map<BaseClassName, FactoryMap> BaseToFactoryMapMap;
结构:typeid(Base).name() → { "Dog" → MetaObject*, "Cat" → MetaObject*, ... }
所有 ClassLoader 实例共享 这一全局表,通过 MetaObject 的 owner ClassLoader 列表 区分“谁能创建谁”。
2.3 ClassLoader 与库的作用域
isLibraryLoadedByAnybody() :物理上 .so 是否已在进程内 dlopen
isLibraryLoaded()(对本 ClassLoader) :库已加载 且 该 loader 拥有对应 MetaObject 的创建权限
多个 ClassLoader 打开同一库时,第二个 loader 不会重复 dlopen,只会 addOwningClassLoader
3. 插件注册机制 3.1 CLASS_LOADER_REGISTER_CLASS 宏 1 2 3 4 5 6 7 8 9 10 11 12 #define CLASS_LOADER_REGISTER_CLASS_INTERNAL_WITH_MESSAGE(Derived, Base, UniqueID, Message) \ namespace \ { \ struct ProxyExec ## UniqueID \ { \ ProxyExec ## UniqueID() \ { \ class_loader::impl::registerPlugin<_derived, _base>(#Derived, #Base); \ } \ }; \ static ProxyExec ## UniqueID g_register_plugin_ ## UniqueID; \ } // namespace
机制:
插件 .cpp 末尾写 CLASS_LOADER_REGISTER_CLASS(Dog, Base)
生成 静态全局对象 g_register_plugin_N
dlopen 加载 .so 时 ,静态对象构造 → 调用 registerPlugin
registerPlugin 创建 MetaObject 并插入全局 FactoryMap
测试插件示例(test/plugins1.cpp):
1 2 3 4 5 CLASS_LOADER_REGISTER_CLASS(Dog, Base) CLASS_LOADER_REGISTER_CLASS(Cat, Base) CLASS_LOADER_REGISTER_CLASS(Duck, Base) CLASS_LOADER_REGISTER_CLASS(Cow, Base) CLASS_LOADER_REGISTER_CLASS(Sheep, Base)
3.2 loadLibrary 时的上下文 loadLibrary 在 dlopen 前设置:
1 2 setCurrentlyActiveClassLoader(loader); setCurrentlyLoadingLibraryName(library_path);
这样 registerPlugin 能把 MetaObject 绑定到正确的 ClassLoader 和 库路径 。
4. 实例创建与生命周期 4.1 三种创建方式
方法
返回类型
析构
适用
createInstance<Base>()
shared_ptr<Base>
自定义 deleter → onPluginDeletion
推荐
createUniqueInstance<Base>()
unique_ptr<Base, Deleter>
同上
独占所有权
createUnmanagedInstance<Base>()
Base*
调用方负责
不推荐;会禁用安全 unload
createRawInstance 核心逻辑:
1 2 3 4 5 6 7 8 if (!isLibraryLoaded()) { loadLibrary(); } Base * obj = class_loader::impl::createInstance<Base>(derived_class_name, this); // ... if (managed) { ++plugin_ref_count_; }
4.2 On-Demand Load/Unload 构造参数 ondemand_load_unload(默认 false):
false :构造 ClassLoader 时立即 loadLibrary()
true :首次 createInstance 时加载;最后一个 managed 实例销毁且 plugin_ref_count_==0 时尝试 unloadLibraryInternal
onPluginDeletion 在 shared_ptr/unique_ptr 释放时:
delete obj
--plugin_ref_count_
若 on-demand 且无 unmanaged 实例 → 卸载库
4.3 引用计数
计数器
含义
load_ref_count_
loadLibrary() / unloadLibrary() 调用次数差
plugin_ref_count_
当前存活的 managed 插件实例数
卸载前若 plugin_ref_count_ > 0 会 警告且拒绝 unload 。
5. loadLibrary / unloadLibrary 详细流程 5.1 loadLibrary(class_loader_core.cpp) 1 2 3 4 5 6 7 8 9 10 loadLibrary(path, loader) ├─ 若 isLibraryLoadedByAnybody(path) │ └─ addClassLoaderOwnerForAllExistingMetaObjectsForLibrary ├─ 否则 │ ├─ setCurrentlyActiveClassLoader(loader) │ ├─ SharedLibrary(path) → dlopen,触发静态注册 │ └─ 加入 LoadedLibraryVector └─ Graveyard 处理 ├─ 无新 MetaObject → revivePreviouslyCreateMetaobjectsFromGraveyard └─ 有新 MetaObject → purgeGraveyardOfMetaobjects(delete=true)
Graveyard(墓地) :卸载库时不真正 delete MetaObject,而是移入 graveyard。原因是 RTLD_GLOBAL 下符号可能未真正卸载,再次 dlopen 时静态注册可能不会重跑,需要从 graveyard 复活 工厂。
5.2 unloadLibrary 1 2 3 4 5 6 7 unloadLibrary(path, loader) ├─ 若 hasANonPurePluginLibraryBeenOpened() → 拒绝卸载(全局锁死) ├─ destroyMetaObjectsForLibrary → 从 FactoryMap 移除,移入 graveyard ├─ 若无任何 MetaObject 仍关联该库 │ ├─ SharedLibrary::unload_library() │ └─ 从 LoadedLibraryVector 移除 └─ 否则保留 .so(其他 ClassLoader 仍在用)
6. MultiLibraryClassLoader 路径:multi_library_class_loader.hpp/.cpp
内部维护 map<library_path, ClassLoader*>
loadLibrary(path) 为每个 path 创建一个 ClassLoader
createInstance<Base>(class_name) 遍历所有 loader,找第一个 isClassAvailable 的
createInstance(class_name, library_path) 指定库,避免同名类冲突
pluginlib 的 ClassLoader<T> 底层即 MultiLibraryClassLoader lowlevel_class_loader_(false) (非 on-demand)。
7. 依赖关系 1 2 3 4 class_loader ├── rcpputils::SharedLibrary (dlopen 封装) ├── console_bridge (日志 CONSOLE_BRIDGE_logDebug) └── ament_cmake / ament_cmake_ros
systemLibraryFormat() 委托 rcpputils::get_platform_library_name() 生成 libfoo.so 等平台名。
8. 符号可见性与“纯插件库”约束 8.1 hide_library_symbols cmake/class_loader_hide_library_symbols.cmake 用 linker version script 隐藏插件库中除注册所需符号外的全部符号,减少与主程序符号冲突。
8.2 非纯插件库问题 若插件库被可执行文件直接链接 (而非仅 dlopen),会在 main 之前触发 dlopen,此时:
getCurrentlyActiveClassLoader() 为 nullptr
设置 hasANonPurePluginLibraryBeenOpened(true)
之后任何库都无法安全 unload
registerPlugin 中会打印 SEVERE WARNING 提示将插件隔离到独立 .so。
9. 与 ROS 2 的集成 9.1 分层关系 1 2 3 4 5 6 7 应用 (rviz / nav2 / ...) ↓ pluginlib::ClassLoader<T> ← plugin.xml + ament_index 解析 ↓ class_loader::MultiLibraryClassLoader ↓ class_loader::ClassLoader + dlopen
PLUGINLIB_EXPORT_CLASS 就是 CLASS_LOADER_REGISTER_CLASS 的别名:
1 2 #define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type) \ CLASS_LOADER_REGISTER_CLASS(class_type, base_class_type)
9.2 rclcpp_components(组件节点) 不经过 pluginlib XML,直接注册:
1 2 3 4 #define RCLCPP_COMPONENTS_REGISTER_NODE(NodeClass) \ CLASS_LOADER_REGISTER_CLASS( \ rclcpp_components::NodeFactoryTemplate<NodeClass>, \ rclcpp_components::NodeFactory)
ComponentManager 用 class_loader::ClassLoader 加载 libxxx_component.so,按类名创建 NodeFactory 再构造节点。
9.3 典型使用场景
场景
基类
注册宏
rviz 插件
rviz_common::Display / Tool
PLUGINLIB_EXPORT_CLASS
costmap 层
nav2_costmap_2d::Layer
PLUGINLIB_EXPORT_CLASS
组件节点
rclcpp_components::NodeFactory
RCLCPP_COMPONENTS_REGISTER_NODE
非 ROS 插件
自定义 Base
CLASS_LOADER_REGISTER_CLASS
10. 异常类型
异常
触发条件
LibraryLoadException
dlopen 失败
LibraryUnloadException
dlclose 失败或库未注册
CreateClassException
类名不存在或 factory 不属当前 loader
NoClassLoaderExistsException
MultiLibrary 未 load 指定库
11. 目录与文件对照 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class_loader/ ├── include/class_loader/ │ ├── class_loader.hpp # 用户主 API │ ├── multi_library_class_loader.hpp │ ├── class_loader_core.hpp # impl 命名空间 + 模板 │ ├── meta_object.hpp # MetaObject 工厂 │ ├── register_macro.hpp # CLASS_LOADER_REGISTER_CLASS │ ├── exceptions.hpp │ └── visibility_control.hpp # Windows DLL 导出 ├── src/ │ ├── class_loader.cpp # ClassLoader 方法 │ ├── class_loader_core.cpp # 全局状态 + load/unload │ ├── meta_object.cpp # MetaObject 基类实现 │ └── multi_library_class_loader.cpp ├── cmake/class_loader_hide_library_symbols.cmake ├── class_loader-extras.cmake # 供插件 CMake 调用 hide symbols └── test/ # Dog/Cat 等插件单元测试
12. 设计特点与注意事项
特点
说明
静态注册
依赖 C++ 动态库加载时的静态对象构造,无需 dlsym 查符号
全局工厂表
进程级单例;多 loader 共享,靠 owner 列表隔离
Graveyard
应对 RTLD_GLOBAL 下重复 load/unload 时工厂不重建
typeid 键
跨平台基类名可能 mangled,模板参数 Base 必须一致
managed 实例
shared_ptr deleter 耦合引用计数与 on-demand unload
unmanaged 陷阱
一旦创建,进程内 on-demand unload 可能永久失效
线程安全
recursive_mutex 保护全局表与库向量
13. 推荐阅读顺序
注册与实例化 :register_macro.hpp → test/plugins1.cpp → class_loader.hpp 的 createInstance
全局状态 :class_loader_core.hpp 的 registerPlugin / createInstance
load/unload :class_loader_core.cpp 的 loadLibrary / unloadLibrary / graveyard
ROS 封装 :pluginlib/class_loader.hpp + class_list_macros.hpp
组件节点 :rclcpp_components/register_node_macro.hpp + component_manager.cpp
调试 :impl::printDebugInfoToScreen() 打印已加载库与 MetaObject
14. 与 CycloneDDS / iceoryx 的对比
维度
class_loader
CycloneDDS / iceoryx
职责
C++ 类插件动态实例化
进程间通信
加载对象
.so 中的 C++ 类
无(或 SHM/RTPS 库)
ROS 2 角色
rviz/nav2/组件节点基础设施
中间件传输
如果你希望,我可以把本文写入 ros2doc/ 下对应 markdown,或继续追踪 pluginlib 如何从 plugin.xml 解析出类名并调用 MultiLibraryClassLoader::loadLibrary 的完整链路。