目标:学习使用 pluginlib
创建和加载一个简单的插件。
教程级别:初学者
时间:20 分钟
目录
背景
先决条件
任务
创建基类包
创建插件包
2.1 插件的源代码
2.2 插件声明 XML
2.3 CMake 插件声明
使用插件
构建并运行
摘要
背景
这个教程源自于 http://wiki.ros.org/pluginlib,以及编写和使用一个简单插件教程。
pluginlib
是一个 C++库,用于在 ROS 包内加载和卸载插件。插件是动态可加载的类,它们从运行时库(即共享对象,动态链接库)中加载。使用 pluginlib,您无需显式地将应用程序与包含类的库链接——相反, pluginlib
可以在任何时候打开包含导出类的库,而无需应用程序事先了解库或包含类定义的头文件。插件对于扩展/修改应用程序行为很有用,而无需应用程序源代码。
先决条件
本教程假设您具有基本的 C++知识,并且已经成功安装了 ROS 2。
任务
在本教程中,您将创建两个新的包,一个定义基类,另一个提供插件。基类将定义一个通用的多边形类,然后我们的插件将定义特定的形状。
1 创建基类包
在您的 ros2_ws/src
文件夹中使用以下命令创建一个新的空包:
cxy@ubuntu2404-cxy:~$ cd ~/ros2_ws/src
cxy@ubuntu2404-cxy:~/ros2_ws/src$ ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies pluginlib --node-name area_node polygon_base
going to create a new package
package name: polygon_base
destination directory: /home/cxy/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['cxy <cxy@todo.todo>']
licenses: ['Apache-2.0']
build type: ament_cmake
dependencies: ['pluginlib']
node_name: area_node
creating folder ./polygon_base
creating ./polygon_base/package.xml
creating source and include folder
creating folder ./polygon_base/src
creating folder ./polygon_base/include/polygon_base
creating ./polygon_base/CMakeLists.txt
creating ./polygon_base/src/area_node.cpp
打开您最喜欢的编辑器,编辑 ros2_ws/src/polygon_base/include/polygon_base/regular_polygon.hpp
,然后将以下内容粘贴进去:
#ifndef POLYGON_BASE_REGULAR_POLYGON_HPP // 如果没有定义 POLYGON_BASE_REGULAR_POLYGON_HPP
#define POLYGON_BASE_REGULAR_POLYGON_HPP // 那么就定义 POLYGON_BASE_REGULAR_POLYGON_HPP
namespace polygon_base // 定义命名空间 polygon_base
{
class RegularPolygon // 定义一个名为 RegularPolygon 的类
{
public:
virtual void initialize(double side_length) = 0; // 定义一个纯虚函数 initialize,接受一个 double 类型的参数 side_length
virtual double area() = 0; // 定义一个纯虚函数 area,返回一个 double 类型的值
virtual ~RegularPolygon(){} // 定义一个虚析构函数
protected:
RegularPolygon(){} // 定义一个受保护的构造函数
};
} // 结束命名空间 polygon_base
#endif // 结束条件编译,如果已经定义了 POLYGON_BASE_REGULAR_POLYGON_HPP,那么就不会再执行这个头文件中的代码
上面的代码创建了一个名为 RegularPolygon
的抽象类。需要注意的一点是 initialize 方法的存在。使用 pluginlib
时,需要一个没有参数的构造函数,所以如果类需要任何参数,我们使用 initialize 方法将它们传递给对象。
我们需要使这个头文件可以被其他类使用,因此打开 ros2_ws/src/polygon_base/CMakeLists.txt
进行编辑。在 ament_target_dependencies
命令之后添加以下行:
install(
DIRECTORY include/
DESTINATION include
)
在 ament_package
命令之前添加这个命令:
ament_export_include_directories(
include
)
我们稍后将返回此软件包以编写我们的测试节点。
2. 创建插件包
现在我们将为我们的抽象类编写两个非虚拟实现。在您的 ros2_ws/src
文件夹中使用以下命令创建第二个空包:
ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies polygon_base pluginlib --library-name polygon_plugins polygon_plugins
cxy@ubuntu2404-cxy:~/ros2_ws/src$ ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies polygon_base pluginlib --library-name polygon_plugins polygon_plugins
going to create a new package
package name: polygon_plugins
destination directory: /home/cxy/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['cxy <cxy@todo.todo>']
licenses: ['Apache-2.0']
build type: ament_cmake
dependencies: ['polygon_base', 'pluginlib']
library_name: polygon_plugins
creating folder ./polygon_plugins
creating ./polygon_plugins/package.xml
creating source and include folder
creating folder ./polygon_plugins/src
creating folder ./polygon_plugins/include/polygon_plugins
creating ./polygon_plugins/CMakeLists.txt
creating ./polygon_plugins/include/polygon_plugins/polygon_plugins.hpp
creating ./polygon_plugins/src/polygon_plugins.cpp
creating ./polygon_plugins/include/polygon_plugins/visibility_control.h
2.1 插件的源代码
打开 ros2_ws/src/polygon_plugins/src/polygon_plugins.cpp
进行编辑,并将以下内容粘贴进去:
// 引入polygon_plugins头文件
#include "polygon_plugins/polygon_plugins.hpp"
// 引入regular_polygon头文件
#include <polygon_base/regular_polygon.hpp>
// 引入cmath库,用于进行数学运算
#include <cmath>
// 定义polygon_plugins命名空间
namespace polygon_plugins
{
// 定义Square类,继承自RegularPolygon类
class Square : public polygon_base::RegularPolygon
{
public:
// 重写initialize方法,用于初始化边长
void initialize(double side_length) override
{
side_length_ = side_length;
}
// 重写area方法,计算正方形的面积
double area() override
{
return side_length_ * side_length_;
}
protected:
// 定义边长属性
double side_length_;
};
// 定义Triangle类,继承自RegularPolygon类
class Triangle : public polygon_base::RegularPolygon
{
public:
// 重写initialize方法,用于初始化边长
void initialize(double side_length) override
{
side_length_ = side_length;
}
// 重写area方法,计算三角形的面积
double area() override
{
return 0.5 * side_length_ * getHeight();
}
// 定义getHeight方法,计算三角形的高
double getHeight()
{
return sqrt((side_length_ * side_length_) - ((side_length_ / 2) * (side_length_ / 2)));
}
protected:
// 定义边长属性
double side_length_;
};
}
// 引入pluginlib库的class_list_macros头文件
#include <pluginlib/class_list_macros.hpp>
// 使用PLUGINLIB_EXPORT_CLASS宏,导出Square类
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
// 使用PLUGINLIB_EXPORT_CLASS宏,导出Triangle类
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)
正方形和三角形类的实现相当直接:保存边长,并使用它来计算面积。唯一特定于 pluginlib 的部分是最后三行,它调用了一些神奇的宏,将类注册为实际的插件。让我们来看一下 PLUGINLIB_EXPORT_CLASS
宏的参数:
插件类的完全限定类型,在这种情况下为
polygon_plugins::Square
。基类的完全限定类型,在这种情况下为
polygon_base::RegularPolygon
。
2.2 插件声明 XML
上述步骤允许在加载包含库时创建插件实例,但插件加载器仍然需要一种方法来找到该库,并知道在该库内引用什么。为此,我们还将创建一个 XML 文件,该文件与包清单中的特殊导出行一起,使我们的插件的所有必要信息可供 ROS 工具链使用。
创建 ros2_ws/src/polygon_plugins/plugins.xml
,使用以下代码:
<!-- 定义一个名为polygon_plugins的库 -->
<library path="polygon_plugins">
<!-- 定义一个名为Square的类,该类的类型为polygon_plugins::Square,基类类型为polygon_base::RegularPolygon -->
<class type="polygon_plugins::Square" base_class_type="polygon_base::RegularPolygon">
<!-- 对Square类的描述 -->
<description>这是一个正方形插件。</description>
</class>
<!-- 定义一个名为Triangle的类,该类的类型为polygon_plugins::Triangle,基类类型为polygon_base::RegularPolygon -->
<class type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
<!-- 对Triangle类的描述 -->
<description>这是一个三角形插件。</description>
</class>
</library>
几点需要注意:
`<
library
>标签提供了一个库的相对路径,该库包含我们想要导出的插件。在 ROS 2 中,这只是库的名称。在 ROS 1 中,它包含前缀lib
或有时是lib/lib
(即lib/libpolygon_plugins
),但在这里它更简单。``<
class
>标签声明了我们想要从库中导出的插件。让我们来看一下它的参数:`
type
:插件的完全限定类型。对我们来说,那就是polygon_plugins::Square
。
base_class
:插件的完全限定基类类型。对我们来说,那就是polygon_base::RegularPolygon
。
description
:插件的描述及其功能。
2.3 CMake 插件声明
最后一步是通过 CMakeLists.txt
导出您的插件。这是 ROS 1 的一个变化,在 ROS 1 中,导出是通过 package.xml
完成的。在读取 find_package(pluginlib REQUIRED)
的行之后,将以下行添加到您的 ros2_ws/src/polygon_plugins/CMakeLists.txt
中:
pluginlib_export_plugin_description_file(polygon_base plugins.xml)
命令 pluginlib_export_plugin_description_file
的参数是:
包含基类的包,即
polygon_base
。插件声明 xml 的相对路径,即
plugins.xml
。
# 设置CMake的最低版本要求为3.8
cmake_minimum_required(VERSION 3.8)
# 定义项目名称为polygon_plugins
project(polygon_plugins)
# 如果编译器是GNU C++或Clang,则添加编译选项
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# 寻找依赖项
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_ros REQUIRED)
find_package(polygon_base REQUIRED)
find_package(pluginlib REQUIRED)
# 导出插件描述文件
pluginlib_export_plugin_description_file(polygon_base plugins.xml)
# 添加库
add_library(polygon_plugins src/polygon_plugins.cpp)
# 添加库别名
add_library(polygon_plugins::polygon_plugins ALIAS polygon_plugins)
# 设置编译特性,要求C99和C++17
target_compile_features(polygon_plugins PUBLIC c_std_99 cxx_std_17)
# 设置包含目录
target_include_directories(polygon_plugins PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/${PROJECT_NAME}>)
# 设置目标依赖项
ament_target_dependencies(
polygon_plugins
"polygon_base"
"pluginlib"
)
# 设置宏定义,使用dllexport而不是dllimport,适用于构建dll而不是使用它
target_compile_definitions(polygon_plugins PRIVATE "POLYGON_PLUGINS_BUILDING_LIBRARY")
# 安装include目录
install(
DIRECTORY include/
DESTINATION include/${PROJECT_NAME}
)
# 安装目标
install(
TARGETS polygon_plugins
EXPORT export_${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
# 如果构建测试,则找到测试依赖项
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# 跳过检查版权的linter
set(ament_cmake_copyright_FOUND TRUE)
# 跳过cpplint(只在git仓库中工作)
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
# 导出包含目录
ament_export_include_directories(
"include/${PROJECT_NAME}"
)
# 导出库
ament_export_libraries(
polygon_plugins
)
# 导出目标
ament_export_targets(
export_${PROJECT_NAME}
)
# 打包
ament_package()
3. 使用插件
现在是时候使用插件了。这可以在任何包中完成,但我们这里要在base 包中进行。编辑 ros2_ws/src/polygon_base/src/area_node.cpp
以包含以下内容:
// 引入pluginlib的类加载器
#include <pluginlib/class_loader.hpp>
// 引入polygon_base的规则多边形
#include <polygon_base/regular_polygon.hpp>
int main(int argc, char** argv)
{
// 避免未使用参数的警告
(void) argc;
(void) argv;
// 创建一个类加载器,用于加载polygon_base::RegularPolygon类型的插件
pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("polygon_base", "polygon_base::RegularPolygon");
try
{
// 创建一个三角形的实例
std::shared_ptr<polygon_base::RegularPolygon> triangle = poly_loader.createSharedInstance("polygon_plugins::Triangle");
// 初始化三角形,边长为10.0
triangle->initialize(10.0);
// 创建一个正方形的实例
std::shared_ptr<polygon_base::RegularPolygon> square = poly_loader.createSharedInstance("polygon_plugins::Square");
// 初始化正方形,边长为10.0
square->initialize(10.0);
// 打印三角形的面积
printf("Triangle area: %.2f\n", triangle->area());
// 打印正方形的面积
printf("Square area: %.2f\n", square->area());
}
catch(pluginlib::PluginlibException& ex)
{
// 如果插件加载失败,打印错误信息
printf("The plugin failed to load for some reason. Error: %s\n", ex.what());
}
// 返回0,表示程序正常结束
return 0;
}
` ClassLoader
`是关键类,需要理解,定义在` class_loader.hpp
`头文件中https://github.com/ros/pluginlib/blob/ros2/pluginlib/include/pluginlib/class_loader.hpp :
它是以基类为模板的,即
polygon_base::RegularPolygon
。第一个参数是基类包名称的字符串,即
polygon_base
。第二个参数是一个字符串,它是插件的完全限定基类类型,即
polygon_base::RegularPolygon
。
有多种方法可以实例化类的一个实例。在这个例子中,我们使用共享指针。我们只需要用插件类的完全限定类型来调用 createSharedInstance
,在这种情况下是 polygon_plugins::Square
。
重要提示:定义此节点的 polygon_base
包不依赖于 polygon_plugins
类。插件将动态加载,无需声明任何依赖。此外,我们正在用硬编码的插件名称实例化类,但您也可以使用参数等动态地进行实例化。
3. 构建并运行
导航回到您的工作区根目录, ros2_ws
,然后构建您的新包:
cxy@ubuntu2404-cxy:~/ros2_ws$ colcon build --packages-select polygon_base polygon_plugins
Starting >>> polygon_base
Finished <<< polygon_base [6.14s]
Starting >>> polygon_plugins
Finished <<< polygon_plugins [3.41s]
Summary: 2 packages finished [9.78s]
从 ros2_ws
开始,一定要找到设置文件:
source install/setup.bash
现在运行节点:它应该打印:
cxy@ubuntu2404-cxy:~/ros2_ws$ ros2 run polygon_base area_node
Triangle area: 43.30
Square area: 100.00
摘要
恭喜你!你刚刚编写并使用了你的第一个插件。