pluginlib 源码详细分析
工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros/pluginlib
版本:5.1.4(pluginlib 包),许可证 BSD。
pluginlib 在 class_loader 之上封装 ROS/ament 的插件发现与清单机制:插件提供方在构建时注册 XML 描述文件,运行时用 pluginlib::ClassLoader<T> 按 lookup 名加载 .so 并实例化插件类。ROS 2 中 rviz、nav2、image_transport 等大量扩展点都依赖它。
1. 仓库结构
1 | pluginlib/ |
特点:pluginlib 本身是 INTERFACE 库(无 .cpp 编译进库),实现全在头文件 class_loader_imp.hpp 中。
2. 与 class_loader 的分工
| 层次 | 职责 |
|---|---|
| pluginlib | XML 清单、ament 索引、lookup 名→C++ 类名、库路径搜索 |
| class_loader | dlopen、静态注册、MetaObject 工厂、createInstance |
PLUGINLIB_EXPORT_CLASS 就是 CLASS_LOADER_REGISTER_CLASS 的别名:
1 | #define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type) \ |
3. 插件注册(构建时)
3.1 CMake 宏
插件包在 CMakeLists.txt 中调用:
1 | pluginlib_export_plugin_description_file(rviz "plugin_description.xml") |
宏逻辑(cmake/pluginlib_export_plugin_description_file.cmake):
- 安装 XML 到
share/<package>/... - 累积 ament index 资源内容(相对路径 + 换行)
ament_package()时由pluginlib_package_hook.cmake写入 index
资源名约定:
1 | <plugin_category>__pluginlib__plugin |
例如 rviz 的 Display 插件:rviz__pluginlib__plugin。
资源内容为多行相对路径,如 share/rviz_default_plugins/plugins.xml。
3.2 插件 XML 格式
1 | <library path="test_plugins"> |
| 属性/标签 | 含义 |
|---|---|
<library path="..."> |
库名(非完整路径),如 test_plugins → libtest_plugins.so |
class/@name |
lookup_name(对外插件 ID,可含 /) |
class/@type |
C++ 完整类名(传给 class_loader) |
class/@base_class_type |
基类字符串,须与 ClassLoader 构造参数一致 |
<description> |
人类可读描述 |
也支持根标签 <class_libraries> 包裹多个 <library>。
3.3 插件 .so 内注册
1 | PLUGINLIB_EXPORT_CLASS(test_plugins::Foo, test_base::Fubar) |
type 属性必须与 PLUGINLIB_EXPORT_CLASS 的第一个参数一致。
4. ClassLoader 运行时流程
4.1 构造
1 | ClassLoader<T>::ClassLoader( |
参数说明:
package:插件类别(plugin_category),不是插件所在包名。如 rviz 用"rviz_common"或"rviz",决定查哪个 ament index 资源。base_class:基类类型字符串,用于 XML 过滤。plugin_xml_paths:可选,手动指定 XML 路径;默认从 index 自动发现。
4.2 发现 plugin XML(ament_index)
1 | std::string resource_name = package + "__pluginlib__" + attrib_name; |
遍历所有向该 category 注册了插件的包,收集全部 XML 绝对路径。
4.3 解析 XML → ClassDesc
processSingleXMLPluginFile 用 tinyxml2 解析,仅当 base_class_type == base_class_ 时插入 classes_available_:
1 | classes_available.insert(std::pair<std::string, ClassDesc>(lookup_name, |
ClassDesc 字段见 class_desc.hpp:lookup_name、derived_class_、library_name_、resolved_library_path_(加载后填充)等。
4.4 加载库并实例化
1 | createUniqueInstance(lookup_name) |
库路径搜索(getAllLibraryPathsToTry)在导出插件的包 prefix 下尝试:
lib/、lib64/、bin/(Windows DLL)- 带/不带
lib前缀、debug 后缀等多种组合
4.5 实例化 API
| 方法 | 所有权 | 说明 |
|---|---|---|
createUniqueInstance |
unique_ptr + 自定义 deleter |
推荐 |
createUnmanagedInstance |
原始指针 | 调用方负责释放与 unload |
createClassInstance |
原始指针 | 已 deprecated |
createSharedInstance |
声明为 shared_ptr |
实现直接 return createUniqueInstance(类型不匹配,实际不宜使用) |
lowlevel_class_loader_(false) 表示不用 class_loader 的 on-demand 模式;库的 load/unload 由 loadLibraryForClass / unloadLibraryForClass 管理。
5. ClassLoaderBase
非模板基类 ClassLoaderBase 暴露所有管理类接口(列举、查询、load/unload),不含 createInstance。便于写不依赖具体插件类型的管理代码。
6. 工具与 CLI
6.1 list_plugins(C++)
1 | pluginlib::ClassLoader<void> cl(argv[1], argv[2]); |
用法:list_plugins <package/category> <base_class_type>。ClassLoader<void> 仅用于调用 getDeclaredClasses(),不能 createInstance。
6.2 ros2 plugin list(Python)
ros2plugin/verb/list.py 直接读 ament index + 解析 XML,不实例化插件。支持:
ros2 plugin list --packagesros2 plugin list --package <name>
7. ROS 2 典型用法:rviz
1 | PluginlibFactory(const QString & package, const QString & base_class_type) |
rviz 插件包侧:
CMakeLists.txt:pluginlib_export_plugin_description_file(...)- 各 Display/Tool
.cpp末尾:PLUGINLIB_EXPORT_CLASS(..., rviz_common::Display) plugins.xml声明 name/type/base_class_type/library path
用户从 UI 选择插件 → PluginlibFactory::make → createInstance(lookup_name)。
8. 依赖关系
1 | pluginlib (INTERFACE) |
9. 测试基础设施
cmake/pluginlib_enable_plugin_testing.cmake 在测试中模拟完整安装布局:
- 临时 prefix +
AMENT_PREFIX_PATH - 注册 mock 的
test_pluginlib__pluginlib__plugin资源 - 构建
test_plugins共享库
测试覆盖:utest、unique_ptr_test、无效 XML、库缺失等。
10. 双名体系(易混淆点)
| 名称 | 来源 | 用途 |
|---|---|---|
| lookup_name | XML class/@name |
API 参数,如 rviz_default_plugins/Grid |
| type / derived_class | XML class/@type |
传给 class_loader 的 C++ 类名 |
| base_class_type | XML + 构造参数 | 过滤插件是否属于当前 ClassLoader |
| plugin_category (package 参数) | CMake pluginlib_export_plugin_description_file 第一参数 |
ament index 资源键 |
lookup_name 与 C++ 类名可以不同;getClassType(lookup_name) 做映射。
11. 异常类型
| 异常 | 场景 |
|---|---|
ClassLoaderException |
包不存在等 |
InvalidXMLException |
XML 格式错误 |
LibraryLoadException |
找不到 .so 或 dlopen 失败 |
CreateClassException |
工厂创建失败(类名/XML/宏不一致) |
LibraryUnloadException |
卸载失败 |
12. 与 rclcpp_components 的区别
| pluginlib | rclcpp_components | |
|---|---|---|
| 清单 | plugin XML + ament index | 无 XML,直接 CLASS_LOADER_REGISTER_CLASS |
| 发现 | 按 category 扫描 | ComponentManager 按库路径加载 |
| 典型用途 | rviz、nav2、costmap 插件 | 同进程多节点组件 |
| 底层 | class_loader | class_loader |
13. 推荐阅读顺序
- 插件作者视角:
class_list_macros.hpp→ 测试test_plugins.xml+test_plugins.cpp→pluginlib_export_plugin_description_file.cmake - 运行时:
class_loader.hppAPI →class_loader_imp.hpp的getPluginXmlPaths→processSingleXMLPluginFile→loadLibraryForClass - 集成示例:
rviz_common/factory/pluginlib_factory.hpp - 底层机制:[class_loader 分析](ros2doc/ros/class_loader 源码详细分析.md)(若已保存)
- CLI:
list_plugins.cpp、ros2plugin/verb/list.py
14. 设计特点小结
| 特点 | 说明 |
|---|---|
| Header-only 实现 | 模板实现在 class_loader_imp.hpp |
| ament index 驱动 | 跨包发现插件,无需硬编码路径 |
| lookup 名解耦 | XML name 与 C++ type 可不同 |
| 多库管理 | 底层 MultiLibraryClassLoader |
| 库路径启发式搜索 | 兼容 lib/lib64/Windows/debug 等 |
| 显式 load/unload | 与 class_loader on-demand 分离,便于多插件共享一库 |
如果你希望,我可以把本文写入 ros2doc/ros/pluginlib 源码详细分析.md,或继续追踪 nav2 / costmap_2d 中 pluginlib 的完整加载链路(从 plugin.xml 到 costmap layer 实例化)。
正在加载留言…