创建和使用插件(C++)
目标: 学习使用 pluginlib
创建和加载简单插件。
教程级别: 初学者
背景
本教程源自 http://wiki.ros.org/pluginlib 和 编写和使用简单插件教程。
pluginlib
是一个用于从 ROS 包内部加载和卸载插件的 C++ 库。插件是可以动态加载的类,它们从运行时库(即共享对象、动态链接库)中加载。使用 pluginlib
,您无需显式地将应用程序与包含这些类的库进行链接 -- 相反,pluginlib
可以在任何时刻打开包含导出类的库,而应用程序不需要提前了解该库或包含类定义的头文件。插件可用于扩展/修改应用程序的行为,而无需应用程序源代码。
任务
在本教程中,您将创建两个新的软件包,一个定义基类,另一个提供插件。基类将定义一个通用的多边形类,然后我们的插件将定义特定的形状。
1 创建基类软件包
在``ros2_ws/src``文件夹中使用以下命令创建一个新的空软件包:
ros2 pkg create --build-type ament_cmake --dependencies pluginlib --node-name area_node --license Apache-2.0 polygon_base
打开您喜欢的编辑器,编辑``ros2_ws/src/polygon_base/include/polygon_base/regular_polygon.hpp``文件,并将以下内容粘贴到其中:
#ifndef POLYGON_BASE_REGULAR_POLYGON_HPP
#define POLYGON_BASE_REGULAR_POLYGON_HPP
namespace polygon_base
{
class RegularPolygon
{
public:
virtual void initialize(double side_length) = 0;
virtual double area() = 0;
virtual ~RegularPolygon(){}
protected:
RegularPolygon(){}
};
} // namespace 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 --dependencies polygon_base pluginlib --library-name polygon_plugins --license Apache-2.0 polygon_plugins
2.1 插件的源代码
打开 ros2_ws/src/polygon_plugins/src/polygon_plugins.cpp
进行编辑,并将以下内容粘贴到其中:
#include <polygon_base/regular_polygon.hpp>
#include <cmath>
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/class_list_macros.hpp>
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 插件声明 XML
上述步骤使得在加载包含插件的库时可以创建插件的实例,但是插件加载器仍然需要找到该库并知道在库中引用的内容。为此,我们还将创建一个XML文件,该文件与包清单中的特殊导出行一起,将关于插件的所有必要信息提供给ROS工具链。
在``ros2_ws/src/polygon_plugins/plugins.xml``中创建以下代码:
<library path="polygon_plugins">
<class type="polygon_plugins::Square" base_class_type="polygon_base::RegularPolygon">
<description>This is a square plugin.</description>
</class>
<class type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
<description>This is a triangle plugin.</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``。
3 使用插件
现在是时候使用插件了。可以在任何包中完成此操作,但是这里我们将在基础包中进行。编辑``ros2_ws/src/polygon_base/src/area_node.cpp``,使其包含以下内容:
#include <pluginlib/class_loader.hpp>
#include <polygon_base/regular_polygon.hpp>
int main(int argc, char** argv)
{
// To avoid unused parameter warnings
(void) argc;
(void) argv;
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");
triangle->initialize(10.0);
std::shared_ptr<polygon_base::RegularPolygon> 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
`头文件 <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
类。插件将会在不需要声明任何依赖关系的情况下进行动态加载。此外,我们通过硬编码插件名称来实例化类,但你也可以使用参数等动态方式来实现。