ROS入门之动态链接库的创建与测试

目录

前言

一、创建动态链接库

1)配置ROS工作空间

2)生成动态链接库

1.定义头文件(example_ros_class_library.h)

2.定义源文件(example_ros_class_library.cpp)

3. 添加头文件路径

4.定义测试文件(example_ros_class_test_main.cpp)

5.修改配置文件

(1)解除#include的注释

(2)添加add_library()函数

(3)添加add_executable()函数

(5)编译

(6)编译结果

6.目标文件以及类的测试

二、测试动态链接库

1)配置ROS工作空间

2) 调用动态链接库

1.导入头文件

2.导入动态链接库

3.编写测试文件(与创建时的测试文件一样,也可以重新编写)

3. 添加头文件路径

4.修改配置文件

 (1)解除#include的注释

(3) 添加add_executable()函数

 5)编译

(6)编译结果

6.目标文件的测试

至此,动态链接库的创建和测试完成,生成了可移植、可共享的链接库

致敬参考资料:

本文源代码:

小悦儿话动态链接库:


前言

本文的实验环境为Ubuntu20.04 + Ros-noetic + vscode

        动态链接库又称为应用程序拓展,通常,需要把大型软件项目分割为多个独立的模块。动态链接的原理就是把程序的模块分割开来,使之成为一个个相互独立的文件,当点击该程序运行时,该程序所依赖的库模块被加载到内存,然后由动态链接器进行链接操作。

一、创建动态链接库

1)配置ROS工作空间

mkdir -p Dynamic_link_library/src  //创建工作空间

cd Dynamic_link_library //定位到工作空间

catkin_make //编译

code . //使用Vscode打开该工作空间

CTRL+SHIFT+B //选择编译器

catkin_make:build //编译时选择catkin_make工具,点击小齿轮,以后默认catkin_make:build在第一个,点击回车进行编译

creating_a_ros_library roscpp std_msgs std_srvs //在src文件夹下创建功能包

CTRL+SHIFT+B //编译一下,以保证创建功能包时输入的依赖项写错

catkin_make:build //编译时选择catkin_make工具

2)生成动态链接库

1.定义头文件(example_ros_class_library.h)

文件路径:Dynamic_link_library/src/creating_a_ros_library/include/example_ros_class_library.h

#ifndef EXAMPLE_ROS_CLASS_H_
#define EXAMPLE_ROS_CLASS_H_

//some generically useful stuff to include...
#include <math.h>
#include <stdlib.h>
#include <string>
#include <vector>

#include <ros/ros.h> //ALWAYS need to include this

//message types used in this example code;  include more message types, as needed
#include <std_msgs/Bool.h> 
#include <std_msgs/Float32.h>
#include <std_srvs/Trigger.h> // uses the "Trigger.srv" message defined in ROS

// define a class, including a constructor, member variables and member functions
class ExampleRosClass
{
public:
    ExampleRosClass(ros::NodeHandle* nodehandle); //"main" will need to instantiate a ROS nodehandle, then pass it to the constructor
    // may choose to define public methods or public variables, if desired
private:
    // put private member data here;  "private" data will only be available to member functions of this class;
    ros::NodeHandle nh_; // we will need this, to pass between "main" and constructor
    // some objects to support subscriber, service, and publisher
    ros::Subscriber minimal_subscriber_; //these will be set up within the class constructor, hiding these ugly details
    ros::ServiceServer minimal_service_;
    ros::Publisher  minimal_publisher_;
    
    double val_from_subscriber_; //example member variable: better than using globals; convenient way to pass data from a subscriber to other member functions
    double val_to_remember_; // member variables will retain their values even as callbacks come and go
    
    // member methods as well:
    void initializeSubscribers(); // we will define some helper methods to encapsulate the gory details of initializing subscribers, publishers and services
    void initializePublishers();
    void initializeServices();
    
    void subscriberCallback(const std_msgs::Float32& message_holder); //prototype for callback of example subscriber
    //prototype for callback for example service
    bool serviceCallback(std_srvs::TriggerRequest& request, std_srvs::TriggerResponse& response);
}; // note: a class definition requires a semicolon at the end of the definition

#endif  // this closes the header-include trick...ALWAYS need one of these to match #ifndef

2.定义源文件(example_ros_class_library.cpp)

文件路径:Dynamic_link_library/src/creating_a_ros_library/src/example_ros_class_library.cpp

#include <example_ros_class_library.h>

//CONSTRUCTOR:  this will get called whenever an instance of this class is created
// want to put all dirty work of initializations here
// odd syntax: have to pass nodehandle pointer into constructor for constructor to build subscribers, etc
ExampleRosClass::ExampleRosClass(ros::NodeHandle* nodehandle):nh_(*nodehandle)
{ // constructor
    ROS_INFO("in class constructor of ExampleRosClass");
    initializeSubscribers(); // package up the messy work of creating subscribers; do this overhead in constructor
    initializePublishers();
    initializeServices();
    
    //initialize variables here, as needed
    val_to_remember_=0.0; 
    
    // can also do tests/waits to make sure all required services, topics, etc are alive
}

//member helper function to set up subscribers;
// note odd syntax: &ExampleRosClass::subscriberCallback is a pointer to a member function of ExampleRosClass
// "this" keyword is required, to refer to the current instance of ExampleRosClass
void ExampleRosClass::initializeSubscribers()
{
    ROS_INFO("Initializing Subscribers");
    minimal_subscriber_ = nh_.subscribe("example_class_input_topic", 1, &ExampleRosClass::subscriberCallback,this);  
    // add more subscribers here, as needed
}

//member helper function to set up services:
// similar syntax to subscriber, required for setting up services outside of "main()"
void ExampleRosClass::initializeServices()
{
    ROS_INFO("Initializing Services");
    minimal_service_ = nh_.advertiseService("example_minimal_service",
                                                   &ExampleRosClass::serviceCallback,
                                                   this);  
    // add more services here, as needed
}

//member helper function to set up publishers;
void ExampleRosClass::initializePublishers()
{
    ROS_INFO("Initializing Publishers");
    minimal_publisher_ = nh_.advertise<std_msgs::Float32>("example_class_output_topic", 1, true); 
    //add more publishers, as needed
    // note: COULD make minimal_publisher_ a public member function, if want to use it within "main()"
}



// a simple callback function, used by the example subscriber.
// note, though, use of member variables and access to minimal_publisher_ (which is a member method)
void ExampleRosClass::subscriberCallback(const std_msgs::Float32& message_holder) {
    // the real work is done in this callback function
    // it wakes up every time a new message is published on "exampleMinimalSubTopic"

    val_from_subscriber_ = message_holder.data; // copy the received data into member variable, so ALL member funcs of ExampleRosClass can access it
    ROS_INFO("myCallback activated: received value %f",val_from_subscriber_);
    std_msgs::Float32 output_msg;
    val_to_remember_ += val_from_subscriber_; //can use a member variable to store values between calls; add incoming value each callback
    output_msg.data= val_to_remember_;
    // demo use of publisher--since publisher object is a member function
    minimal_publisher_.publish(output_msg); //output the current value of val_to_remember_ 
}


//member function implementation for a service callback function
bool ExampleRosClass::serviceCallback(std_srvs::TriggerRequest& request, std_srvs::TriggerResponse& response) {
    ROS_INFO("service callback activated");
    response.success = true; // boring, but valid response info
    response.message = "here is a response string";
    return true;
}

3. 添加头文件路径

这时候会报错 :显示找不到example_ros_class_test_main.h文件,这时候只需要在c_cpp_properties.json文件中添加路径即可


  "configurations": [
    {
      "browse": {
        "databaseFilename": "${workspaceFolder}/.vscode/browse.vc.db",
        "limitSymbolsToIncludedHeaders": false
      },
      "includePath": [
        "/opt/ros/noetic/include/**",
        "/usr/include/**",
        "src/creating_a_ros_library/include" #example_ros_class_library.h文件的路径
      ],
      "name": "ROS",
      "intelliSenseMode": "gcc-x64",
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "gnu11",
      "cppStandard": "c++14"
    }
  ],
  "version": 4
}

4.定义测试文件(example_ros_class_test_main.cpp)

文件路径:Dynamic_link_library/src/creating_a_ros_library/src/example_ros_class_test_main.cpp

#include <example_ros_class_library.h>

int main(int argc, char** argv) 
{
    // ROS set-ups:
    ros::init(argc, argv, "example_lib_test_main"); //node name

    ros::NodeHandle nh; // create a node handle; need to pass this to the class constructor

    ROS_INFO("main: instantiating an object of type ExampleRosClass");
    ExampleRosClass exampleRosClass(&nh);  //instantiate an ExampleRosClass object and pass in pointer to nodehandle for constructor to use

    ROS_INFO("main: going into spin; let the callbacks do all the work");
    ros::spin();
    return 0;
} 

5.修改配置文件

(1)解除#include的注释

需要找到头文件对其编译,头文件位于如下目录:include/example_ros_class_library.h

include_directories(
include #相对路径
  ${catkin_INCLUDE_DIRS}
)

(2)添加add_library()函数

该函数用于构建动态链接库,主要作用就是将指定的源文件生成链接文件,然后添加到工程中去。

## Declare a C++ library
add_library(example_ros_class_library #动态链接库的文件名
  src/example_ros_class_library.cpp #example_ros_class_library.h的实现文件
)

(3)添加add_executable()函数

该函数用于生成可执行文件

add_executable(example_ros_class_test_main #目标文件名
src/example_ros_class_test_main.cpp #主程序文件
src/example_ros_class_library.cpp #example_ros_class_library.h的实现文件
)

该指令的作用为将目标文件与库文件进行链接。

target_link_libraries(example_ros_class_test_main #目标文件
  ${catkin_LIBRARIES}
)

(5)编译

CTRL+SHIFT+B 

catkin_make:build //编译时选择catkin_make工具

(6)编译结果

编译之后将生成目标文件:devel/lib/creating_a_ros_library/example_ros_class_test_main

生成动态链接库:devel/lib/libexample_ros_class_library.so

6.目标文件以及类的测试

打开一个新的终端并启动节点管理器,节点管理器时ros节点运行的前提

roscore //启动节点管理器

 重新打开一个终端运行目标文件

cd Dynamic_link_library //定位到工作空间

source devel/setup.bash //在终端中进行程序注册,只有在终端注册的程序才能运行

rosrun creating_a_ros_library example_ros_class_test_main //运行目标文件

运行结果如下:

[ INFO] [1646141555.509214060]: main: instantiating an object of type ExampleRosClass
[ INFO] [1646141555.511013435]: in class constructor of ExampleRosClass
[ INFO] [1646141555.514232746]: Initializing Subscribers
[ INFO] [1646141555.515373232]: Initializing Publishers
[ INFO] [1646141555.515601586]: Initializing Services
[ INFO] [1646141555.515867142]: main: going into spin; let the callbacks do all the work

二、测试动态链接库

1)配置ROS工作空间

mkdir -p Dynamic_link_library_test/src  //创建工作空间

cd Dynamic_link_library_test //定位到工作空间

catkin_make //编译

code . //使用Vscode打开该工作空间

CTRL+SHIFT+B //选择编译器

catkin_make:build //编译时选择catkin_make工具,点击小齿轮,以后默认catkin_make:build在第一个,点击回车进行编译

using_dynamic_link_library roscpp std_msgs std_srvs //创建功能包

CTRL+SHIFT+B //编译一下,以保证创建功能包时输入的依赖项写错

catkin_make:build //编译时选择catkin_make工具

2) 调用动态链接库

1.导入头文件

这里无需重新编写头文件,也不能重新编写头文件,因为头文件与创建的动态链接库是对应的

将创建时编写的头文件拷贝到功能包的include目录下:

src/using_dynamic_link_library/include/example_ros_class_library.h

2.导入动态链接库

将创建动态链接库时生成的libexample_ros_class_library.so文件添加到lib路径,在功能包文件夹

using_dynamic_link_librar下新建lib文件夹,将动态链接库拷贝到lib文件夹。

src/using_dynamic_link_library/lib/libexample_ros_class_library.so

3.编写测试文件(与创建时的测试文件一样,也可以重新编写)

将测试文件放到src/using_dynamic_link_library/src/dynamic_link_libaray_test.cpp路径下

#include <example_ros_class_library.h>
 
int main(int argc, char** argv) 
{
    // ROS set-ups:
    ros::init(argc, argv, "dynamic_link_libaray_test"); //node name
 
    ros::NodeHandle nh; // create a node handle; need to pass this to the class constructor
 
    ROS_INFO("main: instantiating an object of type ExampleRosClass");
    ExampleRosClass exampleRosClass(&nh);  //instantiate an ExampleRosClass object and pass in pointer to nodehandle for constructor to use
 
    ROS_INFO("main: going into spin; let the callbacks do all the work");
    ros::spin();
    return 0;
} 

3. 添加头文件路径

这时候会报错 :与上文一样,显示找不到example_ros_class_test_main.h文件,这时候只需要在c_cpp_properties.json文件中添加路径即可

{
  "configurations": [
    {
      "browse": {
        "databaseFilename": "${workspaceFolder}/.vscode/browse.vc.db",
        "limitSymbolsToIncludedHeaders": false
      },
      "includePath": [
        "/opt/ros/noetic/include/**",
        "/usr/include/**",
        "src/using_dynamic_link_library/include" #example_ros_class_library.h文件的路径
      ],
      "name": "ROS",
      "intelliSenseMode": "gcc-x64",
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "gnu11",
      "cppStandard": "c++14"
    }
  ],
  "version": 4
}

4.修改配置文件

 (1)解除#include的注释

需要找到头文件对其编译,头文件位于如下目录:include/example_ros_class_library.h

include_directories(
include #相对路径
  ${catkin_INCLUDE_DIRS}
)

该函数用于指定动态链接库的访问路径,注意,link_directories()函数在CMakeLists.txt文件中没有,需要自己手动添加

link_directories(
  lib #指定动态链接库的访问路径
  ${catkin_LIB_DIRS}
)

(3) 添加add_executable()函数

add_executable(dynamic_link_libaray_test src/dynamic_link_libaray_test.cpp)

注意,这里的动态链接库名:example_ros_class_library

动态链接库文件名为:libexample_ros_class_library.so

这里进行链接是仅保留创建时所用到的名称,去掉前缀lib和后缀.so

target_link_libraries(dynamic_link_libaray_test #目标文件名
  ${catkin_LIBRARIES}
  example_ros_class_library #动态链接库名
)

 5)编译

CTRL+SHIFT+B 

catkin_make:build //编译时选择catkin_make工具

(6)编译结果

编译之后将生成目标文件:devel/lib/using_dynamic_link_library/dynamic_link_libaray_test

6.目标文件的测试

打开一个新的终端并启动节点管理器,节点管理器时ros节点运行的前提

roscore //启动节点管理器

 重新打开一个终端运行目标文件

cd Dynamic_link_library_test //定位到工作空间

source devel/setup.bash //在终端中进行程序注册,只有在终端注册的程序才能运行

rosrun using_dynamic_link_library dynamic_link_libaray_test //运行目标测试文件

运行结果如下:

[ INFO] [1646181753.396777876]: main: instantiating an object of type ExampleRosClass
[ INFO] [1646181753.399042680]: in class constructor of ExampleRosClass
[ INFO] [1646181753.400294847]: Initializing Subscribers
[ INFO] [1646181753.401398260]: Initializing Publishers
[ INFO] [1646181753.401643441]: Initializing Services
[ INFO] [1646181753.401865113]: main: going into spin; let the callbacks do all the work

至此,动态链接库的创建和测试完成,生成了可移植、可共享的链接库

 最后,讨论一下关于lib文件夹位置的问题:,一种是可移植的,一种不可移植

方法1:创建动态链接库时.so文件的默认路径是:devel/lib/功能名/lib链接库名.so

在使用时依旧可以放到该路径下,但是,这时在link_directories()中要包含链接库的绝对路径,否则编译器找不到该动态链接库。

link_directories(
  /home/用户名/工作空间名/src/功能包名/lib/libexample_ros_class_library.so
  ${catkin_LIB_DIRS}
)

方法二:在使用动态链接库时,在src/using_dynamic_link_library/lib路径下建立lib文件夹,将链接库放入其中,这时在link_directories()中既可以包含链接库的绝对路径,也可以包含相对路径,因为此时include/header.h文件、src/source.cpp文件以及lib/link_library.so文件均在同一功能包之下,故仅需包含相对路径即可。

link_directories(
  lib
  ${catkin_LIB_DIRS}
)

仿佛两种方法都能实现动态链接库的调用功能,但是,通过绝对路径调用动态链接库后,把代码分享给别人后,因为路径不同,编译器是找不到动态链接库的,进而使得代码工程的移植性变差,最好的办法是在功能包中新建lib文件夹,这样便可以很方便的进行功能包移植。

致敬参考资料:

1.《ROS机器人编程原理与应用》 作者:Wyatt S.Newman 本文代码出于此书
2.【ROS】动态链接库(.so文件)的生成和调用   作者:yanghq13

本文源代码:

ROS学习过程中遇到的疑难问题--Gitee仓库


小悦儿话动态链接库:

        本文介绍了如何在ROS环境下创建、移植并使用动态链接库,动态链接库有很多优点,已有诸多博客来介绍。关于使用动态链接库,一方面可以节省内存,运行效率高,另一方面,可以将涉及商业价值的功能模块通过动态链接库以及头文件的形式共享给使用者,既不影响使用,又不用担心自己的源文件泄露。

        两个程序的区别在于:一个是通过.h文件+.cpp文件实现节点功能的,一个是通过使用.h文件+动态链接库实现节点功能的。

        众所周知,在编程环境漏掉任何一步,后面的都很有可能执行不下去,因此本文尽可能的详细讲解每一个步骤,所以会出现较多的冗余代码。        

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值