创建和使用插件 (c ++) [待校准@7851]

Goal目标: Learn学习使用pluginlib创建和加载简单插件 [待校准@7852]

教程级别: 初学者 [Alyssa@7088]

Minimum Platform: Ardent

背景

本教程源自 http://wiki.ros.org/pluginlib and Writing and Using a Simple Plugin Tutorial[待校准@7854]

pluginlib是一个c + + 库,用于从ROS包中加载和卸载插件。插件是从运行时库加载的动态调用y可加载类 (即共享对象,动态调用y链接库)。使用pluginlib,不必将其应用程序与包含类的库显式链接-相反,pluginlib可以在任何时候打开包含导出类的库,而无需应用程序事先了解该库或包含类定义的头文件。插件可用于扩展/修改应用程序行为,而无需应用程序源文件。 [待校准@7855]

先决条件

本教程假定了基本的c ++ 知识,并且您已经安装了 pluginlib[待校准@7856]

sudo apt-get install ros-foxy-pluginlib

任务

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

1创建基类包 [待校准@7858]

使用以下终端命令在 dev_ws/src 文件夹中创建一个新的空包。 [待校准@7859]

ros2 pkg create --build-type ament_cmake polygon_base --dependencies pluginlib --node-name area_node

打开您最喜欢的编辑器,编辑 dev_ws/src/polygon_base/include/polygon_base/regular_polygon.hpp ,并在其中粘贴以下内容: [待校准@7860]

#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

上面的代码应该是不言自明的...我们正在创建一个调用ed RegularPolygon 的抽象类。需要注意的一件事是存在initialize方法。对于 pluginlib ,类需要一个没有参数的构造函数,因此,如果需要任何参数,我们使用initialize方法来初始化对象。 [待校准@7861]

我们需要让这个标题对其他类可用,所以打开 dev_ws/src/polygon_base/CMakeLists.txt 进行编辑。在 ament_target_dependencies 命令后添加以下行。 [待校准@7862]

install(
  DIRECTORY include/
  DESTINATION include
)

并将此命令添加到 ament_package 命令之前 [待校准@7863]

ament_export_include_directories(
  include
)

稍后,我们将返回此包以编写测试节点。 [待校准@7864]

2创建插件包 [待校准@7865]

现在,我们将编写抽象类的两个非虚拟实现。使用以下终端命令在 dev_ws/src 文件夹中创建第二个空包。 [待校准@7866]

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

2.1插件的源代码 [待校准@7867]

打开 dev_ws/src/polygon_plugins/src/polygon_plugins.cpp 进行编辑,并在其中粘贴以下内容: [待校准@7868]

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

正方形和三角形类的实现应该相当简单: 保存边长,并使用它来计算面积。唯一特定于pluginlib的部分是最后三行,它调用一些神奇的宏,将类注册为实际插件。让我们来看看 PLUGINLIB_EXPORT_CLASS 宏的参数: [待校准@7869]

  1. 插件类的完全限定类型,在本例中为 “polygon_plugins:: square”。 [待校准@7870]

  2. 基类的完全限定类型,在本例中为 “polygon_base:: 正则arpolygon”。 [待校准@7871]

2.2插件声明XML [待校准@7872]

上述步骤使我们的插件实例在加载它们存在的库后就可以创建,但是插件加载程序仍然需要一种方法来查找该库并知道在该库中引用什么。为此,我们还将创建一个XML文件,该文件与包清单中的特殊导出行一起,使所有关于我们的插件的必要信息可用于ROS工具链。 [待校准@7873]

使用以下代码创建 dev_ws/src/polygon_plugins/plugins.xml : [待校准@7874]

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

需要注意的几件事: [待校准@7875]

  1. [需手动修复的语法] library 标签给出了包含我们要导出的插件的库的相对路径。在ROS 2中,这只是库的名称。在ROS 1中,它包含前缀 lib 或有时 lib/lib (即 lib/libpolygon_plugins ) 但是这里更简单。 [待校准@7876]

  2. [需手动修复的语法] class 标签声明了一个插件,我们想从我们的库中导出。让我们来看看它的参数: [待校准@7877]

  • type: The fully qualified type of the plugin. For us, that's polygon_plugins::Square. [待校准@7878]

  • base_class: The fully qualified base class type for the plugin. For us, that's polygon_base::RegularPolygon. [待校准@7879]

  • [需手动修复的语法]``description``:对插件及其功能的描述。 [待校准@7880]

  • [需手动修复的语法]``name``:过去有一个名称属性,但不再需要。 [待校准@7881]

2.3 CMake插件声明 [待校准@7882]

最后一步是通过 CMakeLists.txt 导出插件。这与ROS 1不同,后者通过 package.xml 出口。在行读数 find_package(pluginlib REQUIRED) 后,将以下块添加到您的 dev_ws/src/polygon_plugins/CMakeLists.txt [待校准@7883]

add_library(polygon_plugins src/polygon_plugins.cpp)
target_include_directories(polygon_plugins PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
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
)

ament_package 命令之前,添加 [待校准@7884]

ament_export_libraries(
  polygon_plugins
)
ament_export_targets(
  export_${PROJECT_NAME}
)

此CMake命令的参数为 [待校准@7885]

  1. 基类的包,即 polygon_base [待校准@7886]

  2. 插件声明xml的相对路径,即 plugins.xml [待校准@7887]

3使用插件 [待校准@7888]

现在是时候使用插件了。这可以在任何包中完成,但是在这里我们将在基本包中完成。编辑 dev_ws/src/polygon_base/src/area_node.cpp 以包含以下内容: [待校准@7889]

#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 header 中定义。 [待校准@7890]

  • 它是用基类模板化的,即项目完结,系统自动填充内容 [待校准@7891]

  • 第一个参数是基类的包名称的string,即 polygon_base [待校准@7892]

  • 第二个参数是带有插件的完全限定基类类型的string,即项目完结,系统自动填充内容 [待校准@7893]

有多种方法来实例化类的实例。在此示例中,我们使用共享指针。我们只需要使用插件类的完全限定类型调用 createSharedInstance ,在这种情况下为 “polygon_plugins:: square”。 [待校准@7894]

重要提示: 定义此节点的 polygon_base 包不依赖于 polygon_plugins 类。将加载dynami调用y的插件,而无需声明任何依赖项。此外,我们使用硬编码的插件名称实例化类,但是您也可以使用参数等对动态调用y进行实例化。 [待校准@7895]

4构建和运行 [待校准@7896]

导航回工作区的根, dev_ws ,并构建您的新包: [待校准@7897]

colcon build --packages-select polygon_base polygon_plugins

dev_ws ,确保源文件安装文件: [待校准@7898]

. install/setup.bash

现在运行节点: [待校准@7899]

ros2 run polygon_base area_node

它应该打印 [待校准@7900]

Triangle area: 43.30
Square area: 100.00

总结

恭喜您!您刚刚编写并使用了您的第一个插件。 [待校准@7901]