1 前言

在之前已经给出了创建共享库的方法,但是直接应用到ROS还是有一些问题。因此,本文主要解决ROS中创建及使用共享库的方法。包含以下内容:

2 创建共享库

2.1 使用原生 CMakeLists.txt

首先在工作空间创建包 creating_a_ros_library

1
catkin_create_pkg creating_a_ros_library roscpp std_msgs std_srvs

然后添加以下文件到工程中,目录如下:

1
2
3
4
5
6
7
8
9
.
├── CMakeLists.txt
├── include
│   └── creating_a_ros_library
│   └── example_ros_class.h
├── package.xml
└── src
├── example_ros_class.cpp
└── example_ros_class_test_main.cpp

说明:example_ros_class.cpp 是用来创建共享库的文件,而example_ros_class_test_main.cpp则是在本工程中调用共享库的主程序。

点击查看`example_ros_class.h`文件内容
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
// example_ros_class.h header file //
// wsn; Feb, 2015
// include this file in "example_ros_class.cpp"

// here's a good trick--should always do this with header files:
// create a unique mnemonic for this header file, so it will get included if needed,
// but will not get included multiple times
#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
点击查看`example_ros_class.cpp`文件内容
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//example_ros_class.cpp:
//wsn, Jan 2016
//illustrates how to use classes to make ROS nodes
// constructor can do the initialization work, including setting up subscribers, publishers and services
// can use member variables to pass data from subscribers to other member functions

// can test this function manually with terminal commands, e.g. (in separate terminals):
// rosrun example_ros_class example_ros_class
// rostopic echo exampleMinimalPubTopic
// rostopic pub -r 4 exampleMinimalSubTopic std_msgs/Float32 2.0
// rosservice call exampleMinimalService 1


// this header incorporates all the necessary #include files and defines the class "ExampleRosClass"
// #include <creating_a_ros_library/example_ros_class.h>
#include "../include/creating_a_ros_library/example_ros_class.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 square of the received value;
}


//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;
}

CMakeLists.txt中需要修改两处:

  1. include_directories()中的include取消注释(大约120行);
  2. 添加以下内容:
    1
    2
    3
    add_library(example_ros_library SHARED src/example_ros_class.cpp)  # 生成的库在devel/lib目录下
    add_executable(${PROJECT_NAME} src/example_ros_class_test_main.cpp)
    target_link_libraries(${PROJECT_NAME} ${catkin_LIBRARIES} example_ros_library)
点击查看`example_ros_class_test_main.cpp`文件内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <creating_a_ros_library/example_ros_class.h>  // 需要将CMakeLists.txt中的include_directories()中的include前的注释取消掉才能找到

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

编译方法:

1
2
cd catkin_ws
catkin_make

在编译完成后,会在catkin_ws/devel/lib/目录下生成libexample_ros_library.so;在在catkin_ws/devel/lib/creating_a_ros_library/目录下生成creating_a_ros_library可执行文件(node)。

测试:

1
2
3
roscore
# open a new terminal
rosrun creating_a_ros_library creating_a_ros_library

输出结果:

1
2
3
4
5
6
[ INFO] [1661084062.326018473]: main: instantiating an object of type ExampleRosClass
[ INFO] [1661084062.326405817]: in class constructor of ExampleRosClass
[ INFO] [1661084062.326451901]: Initializing Subscribers
[ INFO] [1661084062.327675835]: Initializing Publishers
[ INFO] [1661084062.327898970]: Initializing Services
[ INFO] [1661084062.328322949]: main: going into spin; let the callbacks do all the work

2.2 其他工程调用共享库

创建一个新包using_a_ros_library用来调用共享库。先说一下思路:直接调用creating_a_ros_library包,但是这里用using_a_ros_library来调用,因此需要将creating_a_ros_library先导出,导出的方法就是,在creating_ros_library/CMakeLists.txt中取消注释.

1
2
3
4
5
6
catkin_package(
# creating_a_ros_library中的这里要取消注释,非常重要!大约106行
INCLUDE_DIRS include
# LIBRARIES creating_a_ros_library
CATKIN_DEPENDS roscpp std_msgs std_srvs
# DEPENDS system_lib

再进行编译及生成新包。

1
2
3
4
cd ~/catkin_ws
catkin_make
cd ~/catkin_ws/src
catkin_create_pkg using_a_ros_library roscpp std_msgs std_srvs creating_a_ros_library

注意这里直接将包creating_a_ros_library添加进去,就不用在package.xml中再添加依赖项了。

将上一小节的example_ros_class_test_main.cpp复制到该包中,无需修改。该包的目录如下:

1
2
3
4
5
6
7
8
9
using_a_ros_library
├── CMakeLists.txt
├── include
│   └── using_a_ros_library
├── package.xml
└── src
└── example_ros_class_test_main.cpp

3 directories, 3 files

CMakeLists.txt中需要修改的地方:

1
2
3
4
5
6
7
8
9
10
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES using_a_ros_library
# 取消下一行注释,非常重要!!!大约106行
CATKIN_DEPENDS creating_a_ros_library roscpp std_msgs std_srvs
# DEPENDS system_lib
)

add_executable(${PROJECT_NAME} src/example_ros_class_test_main.cpp)
target_link_libraries(${PROJECT_NAME} ${catkin_LIBRARIES} example_ros_library)

修改完成后,编译及运行:

1
2
3
4
5
cd ~/catkin_ws
catkin_make
roscore
# open a new terminal
rosrun using_a_ros_library using_a_ros_library

输出结果如下:

1
2
3
4
5
6
[ INFO] [1661091625.379485178]: main: instantiating an object of type ExampleRosClass
[ INFO] [1661091625.380032496]: in class constructor of ExampleRosClass
[ INFO] [1661091625.380056233]: Initializing Subscribers
[ INFO] [1661091625.381107898]: Initializing Publishers
[ INFO] [1661091625.381327092]: Initializing Services
[ INFO] [1661091625.381558026]: main: going into spin; let the callbacks do all the work

可以看到结果与上节一致。

3 使用catkin_simple来创建库

3.1 catkin_simple使用

catkin_simple是ETH为了简化ROS的冗余CMakeLists.txt而开发的工具。关于该工具的说明可以参考:

1
2
3
4
5
cd catkin_ws/src
git clone https://github.com/wsnewman/learning_ros_external_packages learning_ros_external_packages
cd ..
catkin_make
alias cs_create_pkg='~/catkin_ws/src/learning_ros_external_packages/cs_create_pkg.py'

上述代码的最后一行会让当前终端识别cs_create_pkg,为了能在所有终端中都能使用,采用以下方法:

1
sudo gedit ~/.bashrc

打开上述文件后添加alias cs_create_pkg='~/catkin_ws/src/learning_ros_external_packages/cs_create_pkg.py',最后再进行

1
source ~/.bashrc

3.2 创建库并调用

1
cs_create_pkg cs_creating_a_ros_library roscpp std_msgs std_srvs

向工程中添加文件,目录为:

1
2
3
4
5
6
7
8
9
10
.
├── CMakeLists.txt
├── include
│   └── cs_creating_a_ros_library
│   └── example_ros_class.h
├── package.xml
├── README.md
└── src
├── example_ros_class.cpp
└── example_ros_class_test_main.cpp

其中,example_ros_class.hexample_ros_class.cpp第2节中的一致,直接复制即可。将2.1节中example_ros_class_test_main.cpp首行修改如下,即调用自己的包。

1
#include <cs_creating_a_ros_library/example_ros_class.h> 

点击查看`CMakeLists.txt`的内容
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
cmake_minimum_required(VERSION 2.8.3)
project(cs_creating_a_ros_library)

find_package(catkin_simple REQUIRED)

#uncomment next line to use OpenCV library
#find_package(OpenCV REQUIRED)

#uncomment the next 2 lines to use the point-cloud library
#find_package(PCL 1.7 REQUIRED)
#include_directories(${PCL_INCLUDE_DIRS})


#uncomment the following 4 lines to use the Eigen library
#find_package(cmake_modules REQUIRED)
#find_package(Eigen3 REQUIRED)
#include_directories(${EIGEN3_INCLUDE_DIR})
#add_definitions(${EIGEN_DEFINITIONS})

catkin_simple()

# example boost usage
# find_package(Boost REQUIRED COMPONENTS system thread)

# C++0x support - not quite the same as final C++11!
# use carefully; can interfere with point-cloud library
# SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")

# Libraries: uncomment the following and edit arguments to create a new library
cs_add_library(cs_example_ros_library src/example_ros_class.cpp)

# Executables: uncomment the following and edit arguments to compile new nodes
# may add more of these lines for more nodes from the same package
cs_add_executable(cs_ros_library_test_main src/example_ros_class_test_main.cpp)

#the following is required, if desire to link a node in this package with a library created in this same package
# edit the arguments to reference the named node and named library within this package
target_link_libraries(cs_ros_library_test_main cs_example_ros_library)

cs_install()
cs_export()

值得注意的有:

  • 上述代码的catkin_simple()会调用find_package(),并将本文件夹下的include 和 catkin包含的头文件目录include_directories调用,等价于第二节。
  • 值得一提的是,cs_export() 对应到原CMakeLists.txt中的 catkin_package(),所以这里已经直接导出了cs_creating_a_ros_library包。

编译:

1
2
cd ~/catkin_ws
catkin_make

编译完成后,在~/catkin_ws/devel/lib/目录下生成了libcs_example_ros_library.so,在~/catkin_ws/devel/lib/cs_creating_a_library/目录下生成了cs_ros_library_test_main节点。

测试:
执行

1
2
roscore
rosrun cs_creating_a_ros_library cs_ros_library_test_main

输出结果如下:

1
2
3
4
5
6
[ INFO] [1661085557.379182902]: main: instantiating an object of type ExampleRosClass
[ INFO] [1661085557.379707676]: in class constructor of ExampleRosClass
[ INFO] [1661085557.379718645]: Initializing Subscribers
[ INFO] [1661085557.380861942]: Initializing Publishers
[ INFO] [1661085557.381083735]: Initializing Services
[ INFO] [1661085557.381317748]: main: going into spin; let the callbacks do all the work

可见,结果与上一节的一致。

3.3 外部工程调用共享库

1
2
cd ~/catkin_ws/src
cs_create_pkg cs_using_a_ros_library roscpp std_msgs std_srvs cs_creating_a_ros_library

注意最后包含了 cs_creating_a_ros_library,这里导致在package.xml中包含了对cs_creating_a_ros_library的依赖,这使得在CMakeLists.txt中无须再通过cs_target_link()来添加已经生成的libcs_example_ros_library.so

工程目录:

1
2
3
4
5
6
7
cs_creating_a_ros_library
├── CMakeLists.txt
├── include
├── package.xml
├── README.md
└── src
└── example_ros_class_test_main.cpp

直接将 3.2 节的example_ros_class_test_main.cpp复制即可,无需修改。在CMakeLists.txt中,仅需添加

1
cs_add_executable(cs_ros_library_extern_test src/example_ros_class_test_main.cpp)

编译及测试:

1
2
3
4
5
cd ~/catkin_ws
catkin_make
roscore
# open a new terminal
rosrun cs_using_a_ros_library cs_ros_library_extern_test

结果如下:

1
2
3
4
5
6
[ INFO] [1661086647.935522480]: main: instantiating an object of type ExampleRosClass
[ INFO] [1661086647.936067988]: in class constructor of ExampleRosClass
[ INFO] [1661086647.936079118]: Initializing Subscribers
[ INFO] [1661086647.937107125]: Initializing Publishers
[ INFO] [1661086647.937326156]: Initializing Services
[ INFO] [1661086647.937551842]: main: going into spin; let the callbacks do all the work

可以看到结果与前述结果一致。

4 总结

本文第2.1节创建了一个动态链接库,并在自己的package中进行了调用;第2.2节创建了一个新pkg来调用2.1节的pkg中的动态链接库。
而第3节则简介了catkin_simple,并用气实现了自定义链接库的生成和调用。经过对比,建议使用catkin_simple来管理CMakeLists.txt,确实可以大幅简化,对于不熟悉的开发人员可以提速。