目标:学习使用pluginlib创建和加载一个简单的插件。
pluginlib是一个C++库,用于从ROS包中加载和卸载插件。 插件是从运行时库(即共享对象、动态链接库)加载的动态可加载类。 使用pluginlib,人们不必显式地将他们的应用程序与包含类的库相链接-相反,pluginlib可以在任何时候打开包含导出类的库,而应用程序事先不知道库或包含类定义的头文件。 插件对于扩展/修改应用程序行为而不需要应用程序源代码是有用的。
本教程假设您具有基本的C++知识,并且安装了pluginlib。
sudo apt-get install ros-humble-pluginlib
在本教程中,您将创建两个新包,一个定义基类,另一个提供插件。 基类将定义一个通用的多边形类,然后我们的插件将定义特定的形状。
使用以下命令在ros2_ws/src
文件夹中创建一个新的空包:
ros2 pkg create --build-type ament_cmake polygon_base --dependencies pluginlib --node-name area_node
打开您最喜欢的编辑器,编辑ros2_ws/src/polygon_base/include/polygon_base/regular_polygon.hpp
,然后将以下内容粘贴到其中:
#ifndef FE937085_F76C_445C_A468_AE9788C87B35
#define FE937085_F76C_445C_A468_AE9788C87B35
namespace polygon_base{
class RegularPolygon{
public:
virtual void initialize(double side_length)=0;
virtual double area() =0;
virtual ~RegularPolygon(){}
protected:
RegularPolygon(){}
};
}
#endif /* FE937085_F76C_445C_A468_AE9788C87B35 */
上面的代码应该是非常不言自明的...我们正在创建一个名为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
)
现在我们要写两个抽象类的非虚实现。 使用以下命令在ros2_ws/src
文件夹中创建第二个空包:
ros2 pkg create --build-type ament_cmake polygon_plugins --dependencies polygon_base pluginlib --library-name polygon_plugins
2.1 Source code for the plugins
打开ros2_ws/src/polygon_plugins/src/polygon_plugins.cpp
进行编辑,并在其中粘贴以下内容:
#include
#include
namespace polygon_plugins
{
class Square : public polygon_base::RegularPolygon
{
public:
void initialize(double side_length) override
{
side_length_ = side_length;
}
double area() override
{
return side_length_ * side_length_;
}
protected:
double side_length_;
};
class Triangle : public polygon_base::RegularPolygon
{
public:
void initialize(double side_length) override
{
side_length_ = side_length;
}
double area() override
{
return 0.5 * side_length_ * getHeight();
}
double getHeight()
{
return sqrt((side_length_ * side_length_) - ((side_length_ / 2) * (side_length_ / 2)));
}
protected:
double side_length_;
};
}
#include
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)
Square和Triangle类的实现应该相当简单:保存边长,并使用它来计算面积。 唯一与pluginlib相关的部分是最后三行,它调用了一些神奇的宏,这些宏将类注册为实际的插件。 让我们来看看PLUGINLIB_EXPORT_CLASS
宏的参数:
插件类的完全限定类型,在本例中为polygon_plugins::Square
。
基类的完全限定类型,在本例中为polygon_base::RegularPolygon
。
2.2 Plugin Declaration XML
上面的步骤使得我们的插件的实例可以在加载它们所在的库后创建,但是插件加载器仍然需要一种方法来找到该库并知道在该库中引用什么。 为此,我们还将创建一个XML文件,该文件与包清单中的一个特殊导出行沿着,使ROS工具链可以使用关于我们的插件的所有必要信息。
使用以下代码创建ros2_ws/src/polygon_plugins/plugins.xml
This is a square plugin.
This is a triangle plugin.
需要注意的几点:
library
标签给出了一个包含我们想要导出的插件的库的相对路径。 在ROS2中,这只是库的名称。在ROS 1中,它包含前缀lib
或有时lib/lib
(即lib/libpolygon_plugins
),但这里更简单。
class
标签声明了一个我们想要从库中导出的插件。 让我们看看它的参数:
type
:插件的完全限定类型。对我们来说,这是#2。
base_class
:插件的完全限定基类类型。对我们来说,这是#2。
description
:插件的描述及其功能。
name
:曾经有一个name属性,但现在不再需要了。
最后一步是通过CMakeLists.txt
导出插件。 这是对ROS 1的更改,其中通过package.xml
完成导出。 将以下代码块添加到您的ros2_ws/src/polygon_plugins/CMakeLists.txt
中,并在行中阅读find_package(pluginlib REQUIRED)
之后:
add_library(polygon_plugins src/polygon_plugins.cpp)
target_include_directories(polygon_plugins PUBLIC
$
$)
ament_target_dependencies(
polygon_plugins
polygon_base
pluginlib
)
pluginlib_export_plugin_description_file(polygon_base plugins.xml)
install(
TARGETS polygon_plugins
EXPORT export_${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
pluginlib_export_plugin_description_file
命令的参数为:
具有基类的包,即polygon_base
。
插件声明xml的相对路径,即plugins.xml
。
最后,在ament_package
命令之前添加:
ament_export_libraries(
polygon_plugins
)
ament_export_targets(
export_${PROJECT_NAME}
)
现在是时候使用插件了。 这可以在任何包中完成,但这里我们要在基本包中完成。 编辑ros2_ws/src/polygon_base/src/area_node.cpp
以包含以下内容:
#include
#include
int main(int argc, char** argv)
{
// To avoid unused parameter warnings
(void) argc;
(void) argv;
pluginlib::ClassLoader poly_loader("polygon_base", "polygon_base::RegularPolygon");
try
{
std::shared_ptr triangle = poly_loader.createSharedInstance("polygon_plugins::Triangle");
triangle->initialize(10.0);
std::shared_ptr square = poly_loader.createSharedInstance("polygon_plugins::Square");
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());
}
return 0;
}
ClassLoader
是要理解的关键类,在class_loader.hpp
中定义 头文件:
它是用基类(即polygon_base::RegularPolygon
)模板化的。
第一个参数是基类的包名字符串,即polygon_base
。
第二个参数是一个字符串,具有插件的完全限定基类类型,即polygon_base::RegularPolygon
。
实例化类的实例有多种方法。 在这个例子中,我们使用共享指针。 我们只需要使用插件类的完全限定类型调用createSharedInstance
,在本例中是polygon_plugins::Square
。
重要提示:在其中定义此节点的polygon_base
包不依赖于polygon_plugins
类。 插件将被动态加载,无需声明任何依赖关系。 此外,我们使用硬编码的插件名称实例化类,但您也可以使用参数等动态地这样做。
导航回工作区的根目录ros2_ws
,然后构建新的包:
colcon build --packages-select polygon_base polygon_plugins
从ros2_ws
开始,确保获取安装文件的源代码:
source install/setup.bash
Now run the node:
ros2 run polygon_base area_node
It should print:
Triangle area: 43.30
Square area: 100.00