创建和使用插件(C++)

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

教程级别: 初学者

**时间:**20分钟

背景

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

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

先决条件

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

任务

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

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 宏的参数:

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

  2. 基类的完全限定类型,在这种情况下为``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>

需要注意的几点:

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

  2. 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``命令的参数为:

  1. 基类的包,即``polygon_base``。

  2. 插件声明 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 类。插件将会在不需要声明任何依赖关系的情况下进行动态加载。此外,我们通过硬编码插件名称来实例化类,但你也可以使用参数等动态方式来实现。

4 构建和运行

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

colcon build --packages-select polygon_base polygon_plugins

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

source install/setup.bash

现在运行节点:

ros2 run polygon_base area_node

应该打印出:

Triangle area: 43.30
Square area: 100.00

总结

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