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

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

教程级别: 初学者

时间: 20 分钟

背景

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

pluginlib 是一个 C++ 库,用于从 ROS 包中加载和卸载插件。

插件是从运行时库(即共享对象、动态链接库)加载的动态可加载类。

使用 pluginlib,您不必明确将应用程序链接到包含类的库 - 相反,pluginlib 可以在任何时候打开包含导出类的库,而无需应用程序事先知道该库或包含类定义的头文件。

插件可用于扩展/修改应用程序行为,而无需应用程序源代码。

先决条件

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

任务

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

1 创建基类包

使用以下命令在您的 ros2_ws/src 文件夹中创建一个新的空包:

ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies pluginlib --node-name area_node 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”的抽象类。 需要注意的一点是初始化方法的存在。 使用“pluginlib”,需要一个没有参数的构造函数,因此如果需要任何类参数,我们使用初始化方法将它们传递给对象。

我们需要让这个头文件可供其他类使用,因此打开“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

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_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),但这里更简单。

  1. class 标签声明了我们要从库中导出的插件。

让我们来看看它的参数:

  • type:插件的完全合格类型。对我们来说,那是 polygon_plugins::Square

  • base_class:插件的完全合格基类类型。对我们来说,那是 polygon_base::RegularPolygon

  • description:插件及其功能的描述。

2.3 CMake 插件声明

最后一步是通过“CMakeLists.txt”导出插件。 这与 ROS 1 不同,ROS 1 的导出是通过“package.xml”完成的。 在“ros2_ws/src/polygon_plugins/CMakeLists.txt”的“find_package(pluginlib REQUIRED)”行后添加以下行:

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 头文件 中定义:

  • 它使用基类模板化,即 polygon_base::RegularPolygon

  • 第一个参数是基类包名称的字符串,即 polygon_base

  • 第二个参数是插件的完全限定基类类型的字符串,即 polygon_base::RegularPolygon

有多种方法可以实例化类的实例。 在这个例子中,我们使用共享指针。 我们只需要使用插件类的完全限定类型(在本例中为“polygon_plugins::Square”)调用“createSharedInstance”。

重要提示:定义此节点的“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

摘要

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