创建和使用插件(C++) [4655]

目标: 学习使用 pluginlib 创建和加载简单插件。 [16712]

教程级别: 初学者 [3951]

**时间:**20分钟 [3069]

背景 [16410]

本教程源自 http://wiki.ros.org/pluginlib编写和使用简单插件教程[4657]

pluginlib 是一个用于从 ROS 包内部加载和卸载插件的 C++ 库。插件是可以动态加载的类,它们从运行时库(即共享对象、动态链接库)中加载。使用 pluginlib,您无需显式地将应用程序与包含这些类的库进行链接 -- 相反,pluginlib 可以在任何时刻打开包含导出类的库,而应用程序不需要提前了解该库或包含类定义的头文件。插件可用于扩展/修改应用程序的行为,而无需应用程序源代码。 [16713]

先决条件 [16411]

本教程假定您具备基本的 C++ 知识,并且已经成功 安装了 ROS 2[16714]

任务 [16427]

在本教程中,您将创建两个新的软件包,一个定义基类,另一个提供插件。基类将定义一个通用的多边形类,然后我们的插件将定义特定的形状。 [16715]

1 创建基类软件包 [16716]

在``ros2_ws/src``文件夹中使用以下命令创建一个新的空软件包: [16717]

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``文件,并将以下内容粘贴到其中: [16718]

#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 方法将它们传递给对象。 [16719]

我们需要使这个头文件对其他类可用,所以打开``ros2_ws/src/polygon_base/CMakeLists.txt``进行编辑。在``ament_target_dependencies``命令之后添加以下行: [16720]

install(
  DIRECTORY include/
  DESTINATION include
)

在``ament_package``命令之前添加此命令: [16721]

ament_export_include_directories(
  include
)

我们将在稍后返回这个软件包来编写我们的测试节点。 [16722]

2 创建插件包 [16723]

现在我们要编写两个非虚拟实现我们的抽象类。在 ros2_ws/src 文件夹中创建第二个空包,使用以下命令: [16724]

ros2 pkg create --build-type ament_cmake --dependencies polygon_base pluginlib --library-name polygon_plugins --license Apache-2.0 polygon_plugins

2.1 插件的源代码 [16725]

打开 ros2_ws/src/polygon_plugins/src/polygon_plugins.cpp 进行编辑,并将以下内容粘贴到其中: [16726]

#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 宏的参数: [16727]

  1. 插件类的完全限定类型,在这种情况下为``polygon_plugins::Square``。 [16728]

  2. 基类的完全限定类型,在这种情况下为``polygon_base::RegularPolygon``。 [16729]

2.2 插件声明 XML [16730]

上述步骤使得在加载包含插件的库时可以创建插件的实例,但是插件加载器仍然需要找到该库并知道在库中引用的内容。为此,我们还将创建一个XML文件,该文件与包清单中的特殊导出行一起,将关于插件的所有必要信息提供给ROS工具链。 [16731]

在``ros2_ws/src/polygon_plugins/plugins.xml``中创建以下代码: [16732]

<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>

需要注意的几点: [16733]

  1. library``标签提供了一个相对路径,指向包含我们要导出的插件的库。在ROS 2中,这只是库的名称。在ROS 1中,它包含前缀``lib``或者有时是``lib/lib``(即``lib/libpolygon_plugins),但在这里更简单。 [16734]

  2. class 标签声明了我们想要从库中导出的插件。让我们来看一下它的参数: [16735]

  • type: 插件的完全限定类型。对于我们来说,那是 polygon_plugins::Square[16736]

  • base_class: 插件的完全限定基类类型。对于我们来说,那是 polygon_base::RegularPolygon[16737]

  • description: 插件的描述以及它的功能。 [16738]

2.3 CMake插件声明 [16739]

最后一步是通过``CMakeLists.txt``导出你的插件。这是与ROS 1不同的地方,ROS 1是通过``package.xml``来导出插件的。在读取``find_package(pluginlib REQUIRED)``这一行后,在你的``ros2_ws/src/polygon_plugins/CMakeLists.txt``中添加以下代码行: [16740]

pluginlib_export_plugin_description_file(polygon_base plugins.xml)

``pluginlib_export_plugin_description_file``命令的参数为: [16741]

  1. 基类的包,即``polygon_base``。 [16742]

  2. 插件声明 xml 的相对路径,即``plugins.xml``。 [16743]

3 使用插件 [16744]

现在是时候使用插件了。可以在任何包中完成此操作,但是这里我们将在基础包中进行。编辑``ros2_ws/src/polygon_base/src/area_node.cpp``,使其包含以下内容: [16745]

#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>`_中: `[16746]

  • 它以基类``polygon_base::RegularPolygon``进行模板化。 [16747]

  • 第一个参数是基类的包名字符串,例如``polygon_base``。 [16748]

  • 第二个参数是插件的完全限定基类类型的字符串,例如``polygon_base::RegularPolygon``。 [16749]

有许多实例化类的方法。在这个例子中,我们使用了共享指针。我们只需要使用完全限定的插件类类型调用 createSharedInstance,在本例中是 polygon_plugins::Square[16750]

重要提示:定义这个节点的 polygon_base 包不依赖于 polygon_plugins 类。插件将会在不需要声明任何依赖关系的情况下进行动态加载。此外,我们通过硬编码插件名称来实例化类,但你也可以使用参数等动态方式来实现。 [16751]

4 构建和运行 [16752]

返回你的工作空间根目录 ros2_ws,并构建你的新包: [16753]

colcon build --packages-select polygon_base polygon_plugins

ros2_ws 中,请确保源码了设置文件: [16754]

source install/setup.bash

现在运行节点: [16456]

ros2 run polygon_base area_node

应该打印出: [16755]

Triangle area: 43.30
Square area: 100.00

总结 [16454]

恭喜!您刚刚编写并使用了您的第一个插件。 [16756]