1 简介
pluginlib是一个C++库,可以实现为一个ROS包动态的加载和卸载插件。这里的插件通常是一些功能类,且以运行时可动态加载的库(如共享对象,动态链接库)的形式存在。借助pluginlib的帮助,用户不必关心自己的应用程序该如何链接包含自己想使用的的class的库(如定义class的头文件在哪里,如何定义的),因为pluginlib会在你调用时自动打开你需要的插件库(Note:需要提前将插件库注册到pluginlib)。使用插件来扩展或者修改应用程序的功能非常方便,不用改动源码重新编译应用程序,通过插件的动态加载即可完成功能的扩展和修改。
2 插件编写
pluginlib利用了C++多态的特性,不同的插件只要使用统一的接口,便可以替换使用。这样用户通过调用在插件中实现的统一的接口函数,不需要更改程序,也不需要重新编译,更换插件即可实现功能修正。
利用pluginlib编写插件的方法大致包括如下四步:
首先,创建工作空间,创建后如下所示
其次,开始编写插件基类,基类所在头文件polygon_base.h放在include/my_plugin_test下即可,
#ifndef POLYGON_BASE_H_
#define POLYGON_BASE_H_
namespace polygon_base
{
class Polygon{
public:
Polygon() {};
virtual ~Polygon() {};
virtual void init(float side_len) = 0;
virtual float area() = 0;
};
};
#endif
2.2 创建插件类
所插件类的头文件polygon_plugin.h在放在include/my_plugin_test目录下。
#ifndef POLYGON_PLUGIN_H_
#define POLYGON_PLUGIN_H_
#include <cmath>
#include <my_plugin_test/polygon_base.h>
namespace polygon_plugin{
class Square: public polygon_base::Polygon{
public:
Square() {};
virtual ~Square() {};
virtual void init(float side_len)
{
this->side_len = side_len;
}
virtual float area()
{
return (side_len * side_len);
}
private:
float side_len;
};
class Triangle: public polygon_base::Polygon{
public:
Triangle() {};
virtual ~Triangle() {};
virtual void init(float side_len)
{
this->side_len = side_len;
}
virtual float area()
{
return 0.5 * (side_len * ( sqrt( (side_len * side_len) - (0.5 * side_len)*(0.5 * side_len) ) ) );
}
private:
float side_len;
};
};
#endif
3 导出插件,并编译为动态链接库
3.1导出插件
利用 pluginlib 库提供的宏操作注册插件,并且编译为动态链接库。
在src目录下添加polygon_plugin.cpp,
#include <pluginlib/class_list_macros.h>
#include <my_plugin_test/polygon_base.h>
#include <my_plugin_test/polygon_plugin.h>
//mark Square and Triangle as the exported class
PLUGINLIB_EXPORT_CLASS(polygon_plugin::Triangle, polygon_base::Polygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugin::Square, polygon_base::Polygon)
3.2 编译为动态链接库
要将插件编译为动态链接库,需要相应修改CMakeLists.txt文件,添加如下几行:
include_directories(
include
${catkin_INCLUDE_DIRS}
)
## Declare a C++ library
add_library(polygon_plugin
src/${PROJECT_NAME}/polygon_plugin.cpp
)
4.1 创建插件描述文件
插件描述文件是一个XML格式的文件,用于存储插件的重要信息(如,插件库路径,插件名称,插件类类型,插件基类类型)。
我们在my_plugin_test目录下,创建名为polygon_plugin.xml的文件,
<library path="lib/libpolygon_plugin">
<class type="polygon_plugin::Triangle" base_class_type="polygon_base::Polygon">
<description>This is a triangle plugin.</description>
</class>
<class type="polygon_plugin::Square" base_class_type="polygon_base::Polygon">
<description>This is a square plugin.</description>
</class>
</library>
这里标签library和其属性path一起定义了主package相对于插件库的路径,一个插件库可以包含多个不同的插件类(如这里是2个插件类)。
这里的标签class用以描述插件库中的插件类,属性type指定插件类的类型(必须全名),属性base_class_type指定插件基类的类型(必须全名),属性description描述插件类的功能。
注意:插件描述文件还有一个标签class_libraries这里没有使用,其可以实现在一个插件描述文件包含多个库,该标签无属性。
4.2 注册插件到ROS系统
为确保pluginlib可以查到ROS系统所有插件,定义插件的package必须显式的指定哪个包导出了什么插件。
这通常在package.xml文件中定义,
<export>
<my_plugin_test plugin="${prefix}/polygon_plugin.xml">
</export>
这里标签my_plugin_test是定义插件基类的package名称,属性plugin是前面定义的插件描述符文件。
注意:如果插件类与基类不在同一package,为了使插件的export生效,还必须添加对插件基类所在package的依赖。
<build_depend>my_plugin_test</build_depend> <run_depend>my_plugin_test</run_depend>
5 check插件是否在ROS下可以查看
在catkin_make执行成功之后,source develop/setup.bash,然后运行如下命令如果能正确看到输出polygon_plugin.xml则ok。
rospack plugins --attrib=plugin my_plugin_test
6 调用插件
6.1 在src目录下创建my_plugin_loader.cpp
#include <ros/ros.h>
#include <pluginlib/class_loader.h>
#include <my_plugin_test/polygon_base.h>
int main(int argc, char ** argv)
{
ros::init(argc, argv, "my_plugin_loader");
ros::NodeHandle nh;
float side_len = 5.0;
std::string param_name = "polygon_plugin";
std::string plugin_class;
if(!nh.getParam(param_name.c_str(), plugin_class))
{
ROS_ERROR("can't get param");
return 0;
}
/** * Define plugin loader object(polygon_loader) for loading my plugin. * param1: the path of plugin package, param2:the base class of plugin class with full name */
pluginlib::ClassLoader<polygon_base::Polygon> polygon_loader("my_plugin_test", "polygon_base::Polygon");
try
{
/*Based on input param to create the corresponding plugin instance by ClassLoader*/
boost::shared_ptr<polygon_base::Polygon> polygon_cal = polygon_loader.createInstance(plugin_class);
polygon_cal->init(side_len);
ROS_INFO("plugin class is %s, area is %f",plugin_class.c_str(), polygon_cal->area());
}
/*catch exception ClassLaoder object(polygon_loader) exception*/
catch(pluginlib::PluginlibException& ex)
{
ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
}
return 0;
}
## Declare a C++ executable
add_executable(my_plugin_loader src/my_plugin_loader.cpp)
## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(my_plugin_test_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
## Specify libraries to link a library or executable target against
target_link_libraries(my_plugin_loader ${catkin_LIBRARIES} )
6.3 创建启动文件
在my_plugin_test目录下创建launch文件夹添加文件my_plugin_loader.launch
<launch>
<!--plugin select-->
<!--<param name="polygon_plugin" value="polygon_plugin::Square"/> -->
<param name="polygon_plugin" value="polygon_plugin::Triangle"/>
<node name="my_plugin_loader" pkg="my_plugin_test" type="my_plugin_loader" output="screen"/>
</launch>
最后目录工作空间目录结构如下所示:
6.4 测试
方式一:通过启动文件
roslaunch my_plugin_test my_plugin_loader.launch
输出:
... logging to /home/siriansu/.ros/log/f05ccba8-a4a7-11e6-9012-001fc69be782/roslaunch-siriansu-nuc-24328.log
Checking log directory for disk usage. This may take awhile.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.
started roslaunch server http://siriansu-nuc:40746/
SUMMARY
========
PARAMETERS
* /polygon_plugin: polygon_plugin::T...
* /rosdistro: kinetic
* /rosversion: 1.12.5
NODES
/
my_plugin_loader (my_plugin_test/my_plugin_loader)
ROS_MASTER_URI=http://localhost:11311
core service [/rosout] found
process[my_plugin_loader-1]: started with pid [24346]
[ INFO] [1478842570.904729245]: plugin class is polygon_plugin::Triangle, area is 10.825317
[my_plugin_loader-1] process has finished cleanly
log file: /home/siriansu/.ros/log/f05ccba8-a4a7-11e6-9012-001fc69be782/my_plugin_loader-1*.log
all processes on machine have died, roslaunch will exit
shutting down processing monitor...
... shutting down processing monitor complete
done
方式二:命令行方式
rosparam set polygon_plugin polygon_plugin::Square
rosrun my_plugin_test my_plugin_loader
输出:
[ INFO] [1478842687.012935017]: plugin class ispolygon_plugin::Square, area is25.000000
rosparam set polygon_plugin polygon_plugin::Triangle
rosrun my_plugin_test my_plugin_loader
输出:
[ INFO] [1478844657.917205466]: plugin class ispolygon_plugin::Triangle, area is10.825317