1. 写在最前面
由于在一个源码中, 关于在ROS平台上的具体实现, 用到了Plugin. 为了更加深入的理解到底是一个什么鬼, 所以对pluginlib
进行了一些学习, 下面的内容, 大部分在网站上是能够找到的, 是综合Wiki上的介绍, 以及从源码中看到的内容和自己的尝试. 希望对大家都一点点帮助.
pluginlib
是一个使用C++实现的库, 用于在ROS包里面动态的加载或卸载plugin. plugin满足一些条件的, 可以从运行库(例如共享对象, 动态链接库)中动态加载的类. Plugin在扩展或修改应用的行为上很有优势, 并不需要知道原始类的源码, 也许你在编写代码时, 也并不知道你将会用到哪个plugin, 而是在运行时通过参数载入才确定具体的plugin, 在后面有一点相关的示例, 可以体会一下使用plugin的feeling(感觉很一般, 哈哈哈…).
2. 示例
想象一个场景, 假设现在有一个ROS包polygon_interface_package
, 里面包含一个polygon基类, 同时, 另外还有两个不同的polygon, 一个是rectangle, 存在于rectangle_plugin
包, 另外一个是triangle, 存在于triangle_plugin
包. 我们希望rectangle和triangle都被系统支持.
2.1 registering/Exporting a Plugin
为了能够动态加载一个类, 那么, 这个类必须是一个被标注的, 并且导入到系统的一个类. 这个过程, 需要使用宏PLUGINLIB_EXPORT_CLASS
来完成. 一般情况下, 这个宏放在实现文件(.cpp)的末尾. 类似如下:
#include <pluginlib/class_list_macros.h>
#include <polygon_interface_package/polygon.h>
#include <rectangle_package/rectangle.h>
PLUGINLIB_EXPORT_CLASS(rectangle_namespace::Rectangle, polygon_namespace::Polygon)
2.2 The Plugin Description File
每个Plugin都需要由一个插件描述文件, 是一个XML格式机器可读的文件. 包含一些必要的信息, 例如路径, 插件类型, 插件名称等. 类似如下:
<library path="lib/librectangle">
<class type="rectangle_namespace::Rectangle" base_class_type="polygon_namespace::Polygon">
<description>
This is a rectangle plugin
</description>
</class>
</library>
更多详细的介绍, 可以查看: http://wiki.ros.org/pluginlib/PluginDescriptionFile
2.3 向ROS Package System中注册插件
为了使pluginlib
能够查询到所有可用的插件, 需要在package.xml文件中添加export
的tag块. 类似如下:
<export>
<polygon_interface_package plugin="${prefix}/rectangle_plugin.xml" />
</export>
一个值得注意的地方, 为了使上述export命令正确执行, 还需要在build和run依赖项中添加如下信息:
<build_depend>polygon_interface_package</build_depend>
<run_depend>polygon_interface_package</run_depend>
2.4 Querying ROS Package System For Available Plugins
可以使用rospack
命令来查询, 就很简单的一条命令, 类似如下:
$ rospack plugins --attrib=plugin nav_core
将会返回所有nav_core
包中导入的插件.
2.5 Step by Step
2.5.1 创建Plugin
首先, 当然是在自己的工作空间中创建一个用于尝试的ROS Package. 依次输入下述命令.
$ roscd
$ cd ../src
$ catkin_create_pkg plugin_test roscpp pluginlib
OK, 现在可以开始写代码了. 在include/plugin_test文件夹下新建文件polygon_base.h, 将下述代码拷贝进去, 申明我们的基类.
#ifndef PLUGINLIB_TUTORIALS__POLYGON_BASE_H_
#define PLUGINLIB_TUTORIALS__POLYGON_BASE_H_
namespace polygon_base
{
class RegularPolygon
{
public:
virtual void initialize(double side_length) = 0;
virtual double area() = 0;
virtual ~RegularPolygon(){}
protected:
RegularPolygon(){}
};
};
#endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
在include/plugin_test文件夹下新建文件polygon_plugins.h, 将下述代码拷贝进去, 申明我们的插件:
#ifndef PLUGINLIB_TUTORIALS__POLYGON_PLUGINS_H_
#define PLUGINLIB_TUTORIALS__POLYGON_PLUGINS_H_
#include <plugin_test/polygon_base.h>
#include <cmath>
namespace polygon_plugins
{
class Triangle : public polygon_base::RegularPolygon
{
public:
Triangle(){}
void initialize(double side_length)
{
side_length_ = side_length;
}
double area()
{
return 0.5 * side_length_ * getHeight();
}
double getHeight()
{
return sqrt((side_length_ * side_length_) - ((side_length_ / 2) * (side_length_ / 2)));
}
private:
double side_length_;
};
class Square : public polygon_base::RegularPolygon
{
public:
Square(){}
void initialize(double side_length)
{
side_length_ = side_length;
}
double area()
{
return side_length_ * side_length_;
}
private:
double side_length_;
};
};
#endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
在src文件夹下创建文件polygon_plugins.cpp, 并拷贝下述代码进去, 注册我们的插件.
#include <pluginlib/class_list_macros.h>
#include <plugin_test/polygon_base.h>
#include <plugin_test/polygon_plugins.h>
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
在CMakeLists.txt文件中, 加入下述add_library申明. 值得注意的是, 在CMakeLists.txt文件中, 需要在include_directories中添加include目录, 否则我们前面写的两个头文件将会找不到.
include_directories(
${catkin_INCLUDE_DIRS}
include
)
... ...
add_library(polygon_plugins src/polygon_plugins.cpp)
如前所述, 咱还需要编辑关于插件的信息内容, 在plugin_test主目录下, 创建一个polygon_plugins.xml文件, 复制下述内容进入:
<library path="lib/libpolygon_plugins">
<class type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
<description>This is a triangle plugin.</description>
</class>
<class type="polygon_plugins::Square" base_class_type="polygon_base::RegularPolygon">
<description>This is a square plugin.</description>
</class>
</library>
在同目录下的package.xml文件中export tag块中添加下述内容:
<plugin_test plugin="${prefix}/polygon_plugins.xml" />
在命令行中运行下述指令, 对应的输出信息如下所示:
$ rospack plugins --attrib=plugin plugin_test
plugin_test /home/silence/WorkSpace/catkin_ws/src/plugin_test/polygon_plugins.xml
如果得到类似的输出, 则表示所有都是没问题的.
2.5.2 使用Plugin
2.5.2.1 基本使用体验
在src目录下, 新建polygon_loader.cpp文件, 用于测试, 复制下述内容:
#include <pluginlib/class_loader.h>
#include <plugin_test/polygon_base.h>
int main(int argc, char** argv)
{
pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("plugin_test", "polygon_base::RegularPolygon");
try
{
boost::shared_ptr<polygon_base::RegularPolygon> triangle = poly_loader.createInstance("polygon_plugins::Triangle");
triangle->initialize(10.0);
boost::shared_ptr<polygon_base::RegularPolygon> square = poly_loader.createInstance("polygon_plugins::Square");
square->initialize(10.0);
ROS_INFO("Triangle area: %.2f", triangle->area());
ROS_INFO("Square area: %.2f", square->area());
}
catch(pluginlib::PluginlibException& ex)
{
ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
并在CMakeLists.txt中加入下述申明, 然后$ catkin_make
.
add_executable(polygon_loader src/polygon_loader.cpp)
target_link_libraries(polygon_loader ${catkin_LIBRARIES})
编译成功后, 运行节点, 应该会得到下述类似的输出.
$ rosrun plugin_test polygon_loader
[ INFO] [1477584281.637794959]: Triangle area: 43.30
[ INFO] [1477584281.637923253]: Square area: 100.00
上述代码都比较简单, 就不做过多说明了.
2.5.2.2 另一种体验
在src目录下, 新建polygon_loader_v1.cpp文件, 用于测试, 复制下述内容:
#include <pluginlib/class_loader.h>
#include <plugin_test/polygon_base.h>
#include <ros/ros.h>
#include <sstream>
#include <string>
#include <vector>
template <typename T>
std::string to_string(T value) {
std::ostringstream os ;
os << value ;
return os.str() ;
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "polygon_loader_v1");
ros::NodeHandle n_;
std::vector<std::string> class_names;
int class_index = 1;
while (true)
{
std::string class_name;
std::string param_name = std::string("polygon_loader_v1/derive_class_" + to_string(class_index++));
if(!n_.getParam(param_name.c_str(), class_name))
break;
class_names.push_back(class_name);
}
if (class_names.empty()) {
ROS_ERROR("ros parameter error");
return 0;
}
pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("plugin_test", "polygon_base::RegularPolygon");
try
{
for (class_index = 0; class_index < class_names.size(); ++class_index) {
std::string class_name = class_names[class_index];
boost::shared_ptr<polygon_base::RegularPolygon> plugin = poly_loader.createInstance(class_name);
plugin->initialize(10.0);
ROS_INFO("The polygon (%d / %d) area: %.2f", class_index + 1, class_names.size(), plugin->area());
}
}
catch(pluginlib::PluginlibException& ex)
{
ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
}
ROS_INFO("Waiting \"Ctrl + C\"");
while (ros::ok()) {
;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
并在CMakeLists.txt中加入下述申明, 然后$ catkin_make
.
add_executable(polygon_loader_v1 src/polygon_loader_v1.cpp)
target_link_libraries(polygon_loader_v1 ${catkin_LIBRARIES})
在plugin_test目录下新建launch文件夹, 创建class_loader.launch文件, 复制下述内容.
<launch>
<param name="polygon_loader_v1/derive_class_1" value="polygon_plugins::Triangle" />
<param name="polygon_loader_v1/derive_class_2" value="polygon_plugins::Square" />
<node name="polygon_loader_v1"
pkg="plugin_test" type="polygon_loader_v1" output="screen" />
</launch>
编译成功后, 运行launch文件, 因为在代码中, 我们初始化了ros节点, 并且在launch文件中我们添加了ros parameter, 发起launch文件后, 会自动开启roscore
, 输出会比较混乱, 应该会得到下述类似的输出.
$ roslaunch plugin_test class_loader.launch
... ...
auto-starting new master
process[master]: started with pid [16890]
ROS_MASTER_URI=http://localhost:11311
setting /run_id to bfd33a96-9cf8-11e6-a0bb-78acc03c5a93
process[rosout-1]: started with pid [16903]
started core service [/rosout]
process[polygon_loader_v1-2]: started with pid [16906]
[ INFO] [1477650287.294727014]: The polygon (1 / 2) area: 43.30
[ INFO] [1477650287.294864723]: The polygon (2 / 2) area: 100.00
[ INFO] [1477650287.294899464]: Waiting "Ctrl + C"
上述代码都比较简单, 就不做过多说明了.
3. 进阶
pluginlib
的源码见链接: https://github.com/ros/pluginlib . 打开include文件夹, 可以看得到, 其中仅包含少量的文件. 截图如下:
3.1 class_list_macros.h
其结构很清晰, 其中几个重要的宏申明于class_list_macros.h
中, 定义如下:
#include <class_loader/class_loader.h>
#define PLUGINLIB_REGISTER_CLASS(class_name, class_type, base_class_type) \
CLASS_LOADER_REGISTER_CLASS_WITH_MESSAGE(class_type, base_class_type, "In file " __FILE__ " pluginlib WARNING: PLUGINLIB_REGISTER_CLASS is deprecated, please use PLUGINLIB_EXPORT_CLASS instead. You can run the script 'plugin_macro_update' provided with pluginlib in your package source folder to automatically and recursively update legacy macros. Base = base_class_type, Derived = derived_class_type")
#define PLUGINLIB_DECLARE_CLASS(pkg, class_name, class_type, base_class_type) \
CLASS_LOADER_REGISTER_CLASS_WITH_MESSAGE(class_type, base_class_type, "pluginlib WARNING: In file " __FILE__ " PLUGINLIB_DECLARE_CLASS is deprecated, please use PLUGINLIB_EXPORT_CLASS instead. You can run the script 'plugin_macro_update' provided with pluginlib in your package source folder to automatically and recursively update legacy macros. Base = base_class_type, Derived = derived_class_type")
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type) \
CLASS_LOADER_REGISTER_CLASS(class_type, base_class_type);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
3.2 class_desc.h
class_desc.h
文件的定义如下, 其主要是用于保存前述xml文件中所编辑的关于plugin的内容.
class ClassDesc
{
public:
ClassDesc(const std::string& lookup_name, const std::string& derived_class, const std::string& base_class, const std::string& package,
const std::string& description, const std::string& library_name, const std::string& plugin_manifest_path):
lookup_name_(lookup_name),
derived_class_(derived_class),
base_class_(base_class),
package_(package),
description_(description),
library_name_(library_name),
resolved_library_path_("UNRESOLVED"),
plugin_manifest_path_(plugin_manifest_path){}
std::string lookup_name_;
std::string derived_class_;
std::string base_class_;
std::string package_;
std::string description_;
std::string library_name_;
std::string resolved_library_path_;
std::string plugin_manifest_path_;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
3.2 class_loader
除了上述两部分外, 另外一个是与违例相关的类申明文件, pluginlib_exceptions.h
. 当然, 其中最重要的部分, 是class loader. class_loader是pluginlib
中最基础的部分. 完成在运行时从运行库中动态的加载已导入的C++类, 即插件, 以及创建这些类的对象. 一般而言, 当插件是为了non-ROS package所创建的时, 应用使用class loader; 而将插件导入到ROS package时, 应该使用pluginlib. class loader主要提供两个接口, 分别是class_loader::ClassLoader
和 class_loader::MultiLibraryClassLoader
. 都提供了类似的接口, 不同之处在于, 前者仅能绑定单个库, 而后者可以绑定多个库. 基本应用示例如下:
#include <class_loader/class_loader.h>
#include "MyBase.h"
int main()
{
class_loader::ClassLoader loader("libMyLibrary.so");
std::vector<std::string> classes = loader.getAvailableClasses<MyBase>();
for(unsigned int c = 0; c < classes.size(); ++c)
{
boost::shared_ptr<MyBase> plugin = loader.createInstance<MyBase>(classes[c]);
plugin->someMethod();
}
}
值得说明的几点:
-
ClassLoader
可以查询到具有某个基类的插件(或类), 并且实例化这些插件, 前提条件是这些插件提前注册并导入了, 即在实现文件末尾加入下述宏定义.
CLASS_LOADER_REGISTER_CLASS(Derived, Base)
-
从上述示例用法中可以看到, 客户端的代码, 虽然不需要子类的具体定义, 但不可避免的是需要共同基类(即MyBase
)的定义. 上述代码中可以看到, 查询可用插件和创建实例的两个接口, 均是模板函数. 如果你所给定的基类不对, 是不能够成功获取到任何东西.
-
允许某个类多次申明, 并具有不同的基类申明
-
class_loader::ClassLoader
和 class_loader::MultiLibraryClassLoader
的所有接口都是线程安全的.
-
模板函数中的模板, 都是静态判定的, 保证获取到的接口是类型准确的. 不会出现加载到具有不兼容接口的无效插件.
最后, 如果对class loader的具体实现感兴趣的朋友, 可以参看下述链接, http://wiki.ros.org/class_loader/Design , 并配合前面给出的Github上的源码进行理解.