在计算机领域,插件是很常用的术语。插件是一种模块化的软件,可以在现有应用软件的基础上增加一些新的功能。
我们不需要再主应用程序中编写所有功能。相反我们只需要在主应用中建立一个软件架构,并接收新的插件。通过这种方法,我们可以任意扩展软件功能。
我们也需要为自己的机器人应用安装一些插件,当我们开始为机器人开发复杂的ROS应用程序时,插件将是我们扩充应用功能的一个不错的选择。
ROS系统提供了pluginlib插件框架,该框架可以动态地加载或卸载插件。插件可以是一个库,也可以是一个类。pluginlib代表一组C++库,该库可以帮助我们编写插件,也可以在需要时加载或卸载某一插件。
plugin文件是一组运行时库,如共享对象库(.so)或动态链接库(.DLL)。它们是在不链接到主应用程序代码的情况下编译生成的。插件是与主应用软件没有任何依赖关系的独立实体。
插件的主要优点是我们可以在不对应用代码做太多修改的情况下扩展应用软件的功能。
下面使用pluginlib创建一个简单的插件,并且可以看到使用ROS pluginlib创建一个插件所涉及的所以步骤。
使用pluginlib创建一个简单的计算器应用。通过使用插件来增加计算器的各个功能。
与编写一段简单的代码相比,使用插件创建一个计算器应用是一项略微烦琐的任务。然而,示例的主要目的是展示如何在不修改主应用代码的情况下为计算器添加新功能。
功能包依赖 主要依赖 pluginlib
$ catkin_create_pkg plugin_calculator pluginlib roscpp std_msgs
成功创建后如下
依赖pluginlib ,会在include文件夹下多创建一个功能包名字的文件夹
在plugin_calculator/include/plugin_calculator文件下创建calculator_base.h头文件
该文件主要功能是 创建一个抽象类作为基类,所有插件都将继承该基类
声明插件中常用的函数或方法
如果基于现有基类实现插件,则不需要这个步骤。比如navigation中的costmap_2d包中已经提供了代价地图costmap_2d::Layer的基类。nav_core包中提供了nav_core::BaseGlobalPlanner、nav_core::BaseLocalPlanner、nav_core::RecoveryBehavior三个基类。分别用于全局路径规划、局部路径规划、复位行为加载。
#ifndef PLUGINLIB_CALCULATOR_CALCULTOR_BASE_H_
#define PLUGINLIB_CALCULATOR_CALCULTOR_BASE_H_
namespace calculator_base //可以在此命名空间中添加更多类来扩展此基类的功能
{
class calc_functions //声明 calc_functions 类 封装了 插件使用的函数
{
public:
//实现的主要方法
virtual void get_numbers(double number1, double number2) = 0;//检索到两个数字作为计算器的输入
virtual double operation() = 0;//定义我们想要执行的数学运算
virtual ~calc_functions(){}
protected:
calc_functions(){}
};
};
#endif
在plugin_calculator/include/plugin_calculator文件下创建calculator_plugins.h头文件
该文件的主要用途是定义计算器插件的完整功能。
这些插件可以命名为Add、Sub、Mul、Div。
每个插件被定义为一个类,它继承了calculator_base.h头文件中的calc_functions类
#ifndef PLUGINLIB_CALCULATOR_CALCULTOR_PLUGINS_H_
#define PLUGINLIB_CALCULATOR_CALCULTOR_PLUGINS_H_
#include //包含了 用于获取计算器的基本函数
#include
namespace calculator_plugins
{
class Add : public calculator_base::calc_functions
{
public:
Add()
{
number1_ = 0;
number2_ = 0;
}
void get_numbers(double number1, double number2)//继承的函数 的定义 用于检索两个数字输入参数的计算
{
try{
number1_ = number1;
number2_ = number2;
}
catch(int e)
{
std::cerr<<"Exception while inputting numbers"<<std::endl;
}
}
double operation() //继承的函数 的定义 执行想要的运算 执行加法运算
{
return(number1_+number2_);
}
private:
double number1_;
double number2_;
};
//同理其它所有插件的定义
class Sub : public calculator_base::calc_functions
{
public:
Sub()
{
number1_ = 0;
number2_ = 0;
}
void get_numbers(double number1, double number2)
{
try{
number1_ = number1;
number2_ = number2;
}
catch(int e)
{
std::cerr<<"Exception while inputting numbers"<<std::endl;
}
}
double operation()
{
return(number1_- number2_);
}
private:
double number1_;
double number2_;
};
class Mul : public calculator_base::calc_functions
{
public:
Mul()
{
number1_ = 0;
number2_ = 0;
}
void get_numbers(double number1, double number2)
{
try{
number1_ = number1;
number2_ = number2;
}
catch(int e)
{
std::cerr<<"Exception while inputting numbers"<<std::endl;
}
}
double operation()
{
return(number1_ * number2_);
}
private:
double number1_;
double number2_;
};
class Div : public calculator_base::calc_functions
{
public:
Div()
{
number1_ = 0;
number2_ = 0;
}
void get_numbers(double number1, double number2)
{
try{
number1_ = number1;
number2_ = number2;
}
catch(int e)
{
std::cerr<<"Exception while inputting numbers"<<std::endl;
}
}
double operation()
{
if(number2_ == 0)
return(0);
else
return(number1_ / number2_);
}
private:
double number1_;
double number2_;
};
};
#endif
到目前为止,创建了一些标准的C++类。现在,将开始执行pluginlib使用中特定的工作
为了动态地加载这个插件,我们必须使用一个特定的宏 PLUGINLIB_EXPORT_CLASS 来导出每个类。
这个宏必须存在于由插件类组成的任何CPP文件中。
已经定义了插件类,并且在这个文件中,仅定义宏语句即可。
在plugin_calculator/src文件夹下创建 calculator_plugins.cpp 文件
在 PLUGINLIB_EXPORT_CLASS 中,需要提供插件的类名和基类
#include
#include
#include
PLUGINLIB_EXPORT_CLASS(calculator_plugins::Add, calculator_base::calc_functions);//在 PLUGINLIB_EXPORT_CLASS 中,需要提供插件的类名和基类
PLUGINLIB_EXPORT_CLASS(calculator_plugins::Sub, calculator_base::calc_functions);
PLUGINLIB_EXPORT_CLASS(calculator_plugins::Mul, calculator_base::calc_functions);
PLUGINLIB_EXPORT_CLASS(calculator_plugins::Div, calculator_base::calc_functions);
其中
#include
首先包含pluginlib宏,允许我们将类注册为插件。
插件加载器节点实现加载每个插件,将数字输入到每个插件后从插件中获取结果
在plugin_calculator/src文件夹下创建 calculator_loader.cpp 文件
在下面有创建插件的描述文件xml,在本cpp中的
要与xml文件中的
一致。
//这些是加载插件必需的头文件
#include
#include
#include
int main(int argc, char** argv)
{
//pluginlib 提供了 ClassLoader 类 ,它位于class_loader.h文件中,用于在运行时加载类。
//需要为加载器和计算器基类提供一个名称作为参数
pluginlib::ClassLoader<calculator_base::calc_functions> calc_loader("pluginlib_calculator", "calculator_base::calc_functions");
try
{
//使用ClassLoader对象创建add类的实例 (pluginlib_calculator和上面的名称一致)
//"pluginlib_calculator/Add" 与calculator_plugins.xml 中的一致
boost::shared_ptr<calculator_base::calc_functions> add = calc_loader.createInstance("pluginlib_calculator/Add");//记住用法即可
//提供输入并在插件中执行操作
add->get_numbers(10.0,10.0);
double result = add->operation();
ROS_INFO("Triangle area: %.2f", result);
}
catch(pluginlib::PluginlibException& ex)
{
ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
}
try
{
boost::shared_ptr<calculator_base::calc_functions> sub = calc_loader.createInstance("pluginlib_calculator/Sub");
sub->get_numbers(10.0,10.0);
double result = sub->operation();
ROS_INFO("Substracted result: %.2f", result);
}
catch(pluginlib::PluginlibException& ex)
{
ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
}
try
{
boost::shared_ptr<calculator_base::calc_functions> mul = calc_loader.createInstance("pluginlib_calculator/Mul");
mul->get_numbers(10.0,10.0);
double result = mul->operation();
ROS_INFO("Multiplied result: %.2f", result);
}
catch(pluginlib::PluginlibException& ex)
{
ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
}
try
{
boost::shared_ptr<calculator_base::calc_functions> div = calc_loader.createInstance("pluginlib_calculator/Div");
div->get_numbers(10.0,10.0);
double result = div->operation();
ROS_INFO("Division result: %.2f", result);
}
catch(pluginlib::PluginlibException& ex)
{
ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
}
return 0;
}
生成计算器、加载器代码之后,下一步必须在名为Plugin Description Flie的XML文件中描述此软件包内的插件列表。插件描述文件包含了软件包所含插件的所有信息,例如类的名称、类的类型、基类等。
插件描述文件是一个基于插件的软件包的重要文件,因为它有助于ROS系统的自动查找、加载插件。它还包含诸如插件描述之类的信息。
第一行中给出了包含插件类的动态链接库的相对路径。这个库就是上面在CMakeLists.txt中添加的编译产物。
<library path="lib/libpluginlib_calculator">
<class name="pluginlib_calculator/Add" type="calculator_plugins::Add" base_class_type="calculator_base::calc_functions">
<description>This is a add plugin.description>
class>
<class name="pluginlib_calculator/Sub" type="calculator_plugins::Sub" base_class_type="calculator_base::calc_functions">
<description>This is a sub plugin.description>
class>
<class name="pluginlib_calculator/Mul" type="calculator_plugins::Mul" base_class_type="calculator_base::calc_functions">
<description>This is a mul plugin.description>
class>
<class name="pluginlib_calculator/Div" type="calculator_plugins::Div" base_class_type="calculator_base::calc_functions">
<description>This is a div plugin.description>
class>
library>
为了让pluginlib在ROS系统查找到所有基于插件的软件包。我们应该在package.xml中导出插件描述文件。如果不包含此插件,ROS系统将无法找到软件包内的插件。
在package.xml中添加export标签,如下:
<export>
<pluginlib_calculator plugin="${prefix}/calculator_plugins.xml" />
export>
无论在编译还是在运行时,当前这个软件包应该直接依赖其自身
标签的名称pluginlib_calculator 应该与插件的基类base_class所在的包对应。
其中plugin属性应设置为指向上面步骤中创建的的XML文件。
与其它普通ROS节点的另一个区别就是CMakeLists.txt文件中包含的编译指令。
为了编译计算器插件和加载器节点,要在CMakeLists.txt 文件中加入下面几行,以构建插件库
## pluginlib_tutorials library
add_library(pluginlib_calculator src/calculator_plugins.cpp)
target_link_libraries(pluginlib_calculator ${catkin_LIBRARIES})
## polygon_loader executable
add_executable(calculator_loader src/calculator_loader.cpp)
target_link_libraries(calculator_loader ${catkin_LIBRARIES})
至此完成了所有配置
可以进行 编译
catkin_make
下面命令将查询软件包中的插件列表
$ rospack plugins --attrib=plugin pluginlib_calculator
启动roscore后 使用如下命令执行 calculator_loader
$ rosrun pluginlib_calculator calculator_loader