class_loader 源码详细分析

class_loader 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros/class_loader
版本:2.2.0package.xml),语言 C++14,许可证 BSD,Quality Level 1

class_loaderROS 无关的运行时插件加载库:通过 dlopen/LoadLibrary 打开 .so/.dll,在库加载时自动注册工厂(MetaObject),运行时按类名 new 出插件实例,无需在编译期链接插件头文件。ROS 2 中 pluginlib 在其之上封装 XML 清单与包名解析;rclcpp_components 则直接用 CLASS_LOADER_REGISTER_CLASS 实现组件节点动态加载。


1. 总体架构

应用 / pluginlib / ComponentManagerclass_loader::impl 全局状态插件 .soOS / rcpputilsloadLibrary触发静态初始化createInstanceunloadLibraryClassLoader / MultiLibraryClassLoaderBaseToFactoryMapMap<br/>基类 → 类名 → MetaObjectLoadedLibraryVector<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. 核心概念

2.1 MetaObject(工厂)

每个可实例化的插件类对应一个 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

机制:

  1. 插件 .cpp 末尾写 CLASS_LOADER_REGISTER_CLASS(Dog, Base)
  2. 生成 静态全局对象 g_register_plugin_N
  3. dlopen 加载 .so 时,静态对象构造 → 调用 registerPlugin
  4. 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 时的上下文

loadLibrarydlopen 前设置:

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 释放时:

  1. delete obj
  2. --plugin_ref_count_
  3. 若 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) 指定库,避免同名类冲突

pluginlibClassLoader<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.cmakelinker 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)

ComponentManagerclass_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. 推荐阅读顺序

  1. 注册与实例化register_macro.hpptest/plugins1.cppclass_loader.hppcreateInstance
  2. 全局状态class_loader_core.hppregisterPlugin / createInstance
  3. load/unloadclass_loader_core.cpploadLibrary / unloadLibrary / graveyard
  4. ROS 封装pluginlib/class_loader.hpp + class_list_macros.hpp
  5. 组件节点rclcpp_components/register_node_macro.hpp + component_manager.cpp
  6. 调试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 的完整链路。

文章互动

阅读 --

留言

0 条留言

正在加载留言…