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

前言

本文的实验环境为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的实现文件
)

(4)添加target_link_libraries()函数

该指令的目的是将目标文件与库文件链接起来。

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

(2)添加link_directories()函数

该函数用于指定动态链接库的访问路径,注意,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)

(4) 添加target_link_libraries()函数

注意,这里的动态链接库名: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文件+动态链接库实现节点功能的。

众所周知,如果在编程环境中遗漏了任何一步,很可能后面的步骤就不会执行了。因此,本文尽可能详细地解释每个步骤,因此会有更多的冗余代码。

版权声明:本文为博主有梦想的小悦儿原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/m0_49080357/article/details/123214848

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2022年3月3日 下午4:37
下一篇 2022年3月3日 下午5:07

相关推荐