pluginlib 源码详细分析

pluginlib 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros/pluginlib
版本:5.1.4pluginlib 包),许可证 BSD

pluginlibclass_loader 之上封装 ROS/ament 的插件发现与清单机制:插件提供方在构建时注册 XML 描述文件,运行时用 pluginlib::ClassLoader<T> 按 lookup 名加载 .so 并实例化插件类。ROS 2 中 rviznav2image_transport 等大量扩展点都依赖它。


1. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pluginlib/
├── pluginlib/ # C++ 核心(header-only INTERFACE 库)
│ ├── include/pluginlib/
│ │ ├── class_loader.hpp # 模板 ClassLoader API
│ │ ├── class_loader_imp.hpp # 实现(~850 行,被 include)
│ │ ├── class_loader_base.hpp # 非模板管理接口
│ │ ├── class_desc.hpp # 插件元数据
│ │ ├── class_list_macros.hpp # PLUGINLIB_EXPORT_CLASS
│ │ ├── exceptions.hpp
│ │ └── impl/split.hpp
│ ├── src/list_plugins.cpp # CLI 列出插件
│ ├── cmake/ # 构建/注册宏
│ └── test/
└── ros2plugin/ # ros2 CLI 插件(Python)
└── ros2plugin/verb/list.py # ros2 plugin list

特点pluginlib 本身是 INTERFACE 库(无 .cpp 编译进库),实现全在头文件 class_loader_imp.hpp 中。


2. 与 class_loader 的分工

插件包 my_plugins构建时运行时 宿主 rviz/nav2解析 XML + ament_indexplugins.xmllibmy_plugins.soPLUGINLIB_EXPORT_CLASSpluginlib_export_plugin_description_fileament_index 资源pluginlib::ClassLoaderclass_loader::MultiLibraryClassLoaderdlopen + MetaObject
层次 职责
pluginlib XML 清单、ament 索引、lookup 名→C++ 类名、库路径搜索
class_loader dlopen、静态注册、MetaObject 工厂、createInstance

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)

3. 插件注册(构建时)

3.1 CMake 宏

插件包在 CMakeLists.txt 中调用:

1
pluginlib_export_plugin_description_file(rviz "plugin_description.xml")

宏逻辑(cmake/pluginlib_export_plugin_description_file.cmake):

  1. 安装 XML 到 share/<package>/...
  2. 累积 ament index 资源内容(相对路径 + 换行)
  3. 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
2
3
4
5
6
<library path="test_plugins">
<class name="test_pluginlib/foo" type="test_plugins::Foo" base_class_type="test_base::Fubar">
<description>This is a foo plugin.</description>
</class>
...
</library>
属性/标签 含义
<library path="..."> 库名(非完整路径),如 test_pluginslibtest_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
2
PLUGINLIB_EXPORT_CLASS(test_plugins::Foo, test_base::Fubar)
PLUGINLIB_EXPORT_CLASS(test_plugins::Bar, test_base::Fubar)

type 属性必须与 PLUGINLIB_EXPORT_CLASS 的第一个参数一致。


4. ClassLoader 运行时流程

4.1 构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ClassLoader<T>::ClassLoader(
std::string package,
std::string base_class,
std::string attrib_name,
std::vector<std::string> plugin_xml_paths)
: ...
lowlevel_class_loader_(false) // 非 on-demand,库由 pluginlib 显式 load/unload
{
ament_index_cpp::get_package_prefix(package_); // 校验 package 存在
if (plugin_xml_paths_.empty()) {
plugin_xml_paths_ = getPluginXmlPaths(package_, attrib_name_);
}
classes_available_ = determineAvailableClasses(plugin_xml_paths_);
}

参数说明:

  • package:插件类别(plugin_category),不是插件所在包名。如 rviz 用 "rviz_common""rviz",决定查哪个 ament index 资源。
  • base_class:基类类型字符串,用于 XML 过滤。
  • plugin_xml_paths:可选,手动指定 XML 路径;默认从 index 自动发现。

4.2 发现 plugin XML(ament_index)

1
2
3
std::string resource_name = package + "__pluginlib__" + attrib_name;
auto plugin_packages_with_prefixes = ament_index_cpp::get_resources(resource_name);
// 每个导出插件的包:prefix + "/" + 相对路径

遍历所有向该 category 注册了插件的包,收集全部 XML 绝对路径。

4.3 解析 XML → ClassDesc

processSingleXMLPluginFiletinyxml2 解析,仅当 base_class_type == base_class_ 时插入 classes_available_

1
2
3
classes_available.insert(std::pair<std::string, ClassDesc>(lookup_name,
ClassDesc(lookup_name, derived_class, base_class_type, package_name, description_str,
library_path, xml_file)));

ClassDesc 字段见 class_desc.hpplookup_namederived_class_library_name_resolved_library_path_(加载后填充)等。

4.4 加载库并实例化

1
2
3
4
5
6
createUniqueInstance(lookup_name)
→ loadLibraryForClass(lookup_name)
→ getClassLibraryPath(lookup_name) # 搜索 lib/ lib64/ bin/ ...
→ lowlevel_class_loader_.loadLibrary(path)
→ lowlevel_class_loader_.createUniqueInstance<T>(getClassType(lookup_name))
# getClassType 返回 XML 中的 type,即 C++ 类名

库路径搜索(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
2
3
4
pluginlib::ClassLoader<void> cl(argv[1], argv[2]);
for (const auto & declared : cl.getDeclaredClasses()) {
std::cout << declared << std::endl;
}

用法: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 --packages
  • ros2 plugin list --package <name>

7. ROS 2 典型用法:rviz

1
2
3
4
5
PluginlibFactory(const QString & package, const QString & base_class_type)
{
class_loader_ = new pluginlib::ClassLoader<Type>(
package.toStdString(), base_class_type.toStdString());
}

rviz 插件包侧:

  1. CMakeLists.txtpluginlib_export_plugin_description_file(...)
  2. 各 Display/Tool .cpp 末尾:PLUGINLIB_EXPORT_CLASS(..., rviz_common::Display)
  3. plugins.xml 声明 name/type/base_class_type/library path

用户从 UI 选择插件 → PluginlibFactory::makecreateInstance(lookup_name)


8. 依赖关系

1
2
3
4
5
6
pluginlib (INTERFACE)
├── class_loader
├── ament_index_cpp # 发现 plugin XML
├── tinyxml2 # 解析 XML
├── rcpputils # 文件系统、库名
└── rcutils # 日志

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

  1. 插件作者视角class_list_macros.hpp → 测试 test_plugins.xml + test_plugins.cpppluginlib_export_plugin_description_file.cmake
  2. 运行时class_loader.hpp API → class_loader_imp.hppgetPluginXmlPathsprocessSingleXMLPluginFileloadLibraryForClass
  3. 集成示例rviz_common/factory/pluginlib_factory.hpp
  4. 底层机制:[class_loader 分析](ros2doc/ros/class_loader 源码详细分析.md)(若已保存)
  5. CLIlist_plugins.cppros2plugin/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 实例化)。

文章互动

阅读 --

留言

0 条留言

正在加载留言…