Creating C++ Libraries (for ament_cmake)
The C++ library block for ament_cmake
TL;DR
When your project exports a library, you might benefit from using the following template.
Note that there is, in general, no reason to define multiple libraries. A single shared library can hold all the
content that you want to export from a package, hence the library named ${PROJECT_NAME}
.
Remember to
Add all exported headers to
include/<PACKAGE_NAME>
otherwise other packages cannot see it.Add all source files of the library to
add_library
.Add all ROS2 dependencies of the library to
ament_target_dependencies
.Add ALL dependencies for which you used
find_package
toament_export_dependencies
, otherwise dependencies might become complex for projects that use your library.Add any other (NOT ROS2) libraries to
target_link_libraries
.
####################################
# CPP Shared Library Block [BEGIN] #
# vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv #
# https://ros2-tutorial.readthedocs.io/en/latest/
# The most common use case is to merge everything you need to export
# into the same shared library called ${PROJECT_NAME}.
add_library(${PROJECT_NAME} SHARED
src/sample_class.cpp
)
ament_target_dependencies(${PROJECT_NAME}
rclcpp
)
target_include_directories(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)
ament_export_dependencies(
rclcpp
Eigen3
Qt5Core
)
target_link_libraries(${PROJECT_NAME}
Qt5::Core
)
install(
DIRECTORY include/
DESTINATION include
)
install(
TARGETS ${PROJECT_NAME}
EXPORT export_${PROJECT_NAME}
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #
# CPP Shared Library Block [END] #
##################################
The base package can be created with
cd ~/ros2_tutorial_workspace/src
ros2 pkg create cpp_package_with_a_library \
--build-type ament_cmake \
--dependencies rclcpp
resulting in the following output
ros2 pkg create output
ros2 pkg create cpp_package_with_a_library \
--build-type ament_cmake \
--dependencies rclcpp
going to create a new package
package name: cpp_package_with_a_library
destination directory: /home/murilo/ROS2_Tutorial/ros2_tutorial_workspace/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['murilo <murilomarinho@ieee.org>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: ['rclcpp']
creating folder ./cpp_package_with_a_library
creating ./cpp_package_with_a_library/package.xml
creating source and include folder
creating folder ./cpp_package_with_a_library/src
creating folder ./cpp_package_with_a_library/include/cpp_package_with_a_library
creating ./cpp_package_with_a_library/CMakeLists.txt
[WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created.
It is recommended to use one of the ament license identitifers:
Apache-2.0
BSL-1.0
BSD-2.0
BSD-2-Clause
BSD-3-Clause
GPL-3.0-only
LGPL-3.0-only
MIT
MIT-0
Library sources
In this step, we’ll work on these.
cpp_package_with_a_library
├── CMakeLists.txt
├── include
│ └── cpp_package_with_a_library
│ └── sample_class.hpp
├── package.xml
└── src
├── sample_class.cpp
├── sample_class_local_node.cpp
├── sample_class_local_node.hpp
└── sample_class_local_node_main.cpp
A class that does a bunch of nothing, but that depends on Eigen3 and Qt, as an example.
1#pragma once
2
3#include <ostream>
4
5#include <QString>
6#include <eigen3/Eigen/Dense>
7
8class SampleClass
9{
10private:
11 int a_private_member_;
12 const QString a_private_qt_string_;
13 const Eigen::MatrixXd a_private_eigen3_matrix_;
14
15public:
16 SampleClass();
17
18 int get_a_private_member() const;
19 void set_a_private_member(int value);
20 std::string to_string() const;
21
22 static double sum_of_squares(const double&a, const double& b);
23};
24
25std::ostream& operator<<(std::ostream& os, const SampleClass& sc);
1#include <cpp_package_with_a_library/sample_class.hpp>
2
3
4
5/**
6 * @brief SampleClass::SampleClass the default constructor.
7 */
8SampleClass::SampleClass():
9 a_private_qt_string_("I am a QString"),
10 a_private_eigen3_matrix_((Eigen::Matrix2d() << 1,2,3,4).finished())
11{
12
13}
14
15/**
16 * @brief SampleClass::get_a_private_member.
17 * @return an int with the value of a_private_member_.
18 */
19int SampleClass::get_a_private_member() const
20{
21 return a_private_member_;
22}
23
24/**
25 * @brief SampleClass::set_a_private_member.
26 * @param value The new value for a_private_member_.
27 */
28void SampleClass::set_a_private_member(int value)
29{
30 a_private_member_ = value;
31}
32
33/**
34 * @brief SampleClass::sum_of_squares.
35 * @param a The first number.
36 * @param b The second number.
37 * @return a*a + 2*a*b + b*b.
38 */
39double SampleClass::sum_of_squares(const double &a, const double &b)
40{
41 return a*a + 2*a*b + b*b;
42}
43
44/**
45 * @brief SampleClass::to_string converts a SampleClass to a std::string representation.
46 * @return a pretty(-ish) std::string representation of the object.
47 */
48std::string SampleClass::to_string() const
49{
50 std::stringstream ss;
51 ss << "Sample_Class:: " << std::endl <<
52 "a_private_member_ = " << std::to_string(a_private_member_) << std::endl <<
53 "a_private_qt_string_ = " << a_private_qt_string_.toStdString() << std::endl <<
54 "a_private_eigen3_matrix_ = " << a_private_eigen3_matrix_ << std::endl;
55 return ss.str();
56}
57
58/**
59 * @brief operator << the stream operator for SampleClass objects.
60 * @param [in/out] the std::ostream to be modified.
61 * @param [in] sc the SampleClass whose representation is to be streamed.
62 * @return the modified os with the added SampleClass string representation.
63 * @see SampleClass::to_string().
64 */
65std::ostream &operator<<(std::ostream &os, const SampleClass &sc)
66{
67 return os << sc.to_string();
68}
Sources for a local node that uses the library
In this step, we’ll work on these.
cpp_package_with_a_library
├── CMakeLists.txt
├── include
│ └── cpp_package_with_a_library
│ └── sample_class.hpp
├── package.xml
└── src
├── sample_class.cpp
├── sample_class_local_node.cpp
├── sample_class_local_node.hpp
└── sample_class_local_node_main.cpp
Just in case you need to have a node, in the same package, that also uses the library exported by this package. Nothing too far from what we have already done.
1#include "sample_class_local_node.hpp"
2
3/**
4 * @brief SampleClassLocalNode::SampleClassLocalNode Default constructor.
5 */
6SampleClassLocalNode::SampleClassLocalNode():
7 rclcpp::Node("sample_class_local_node"),
8 timer_period_(0.5),
9 print_count_(0)
10{
11 timer_ = create_wall_timer(
12 std::chrono::milliseconds(long(timer_period_*1e3)),
13 std::bind(&SampleClassLocalNode::_timer_callback, this)
14 );
15}
16
17/**
18 * @brief SampleClassLocalNode::_timer_callback periodically prints class info using RCLCPP_INFO.
19 */
20void SampleClassLocalNode::_timer_callback()
21{
22 RCLCPP_INFO_STREAM(get_logger(),
23 std::string("sum_of_squares = ") +
24 std::to_string(SampleClass::sum_of_squares(print_count_,print_count_-5))
25 );
26
27 RCLCPP_INFO_STREAM(get_logger(),
28 sample_class_.to_string() +
29 std::to_string(print_count_) +
30 std::string(" times.")
31 );
32 print_count_++;
33}
1#pragma once
2
3#include <rclcpp/rclcpp.hpp>
4#include <cpp_package_with_a_library/sample_class.hpp>
5
6/**
7 * @brief A ROS2 Node that uses the SampleClass within the same package.
8 */
9class SampleClassLocalNode: public rclcpp::Node
10{
11private:
12 SampleClass sample_class_;
13
14 double timer_period_;
15 int print_count_;
16 rclcpp::TimerBase::SharedPtr timer_;
17
18 void _timer_callback();
19public:
20 SampleClassLocalNode();
21
22};
1#include <rclcpp/rclcpp.hpp>
2
3#include "sample_class_local_node.hpp"
4
5int main(int argc, char** argv)
6{
7 rclcpp::init(argc,argv);
8
9 try
10 {
11 auto node = std::make_shared<SampleClassLocalNode>();
12
13 rclcpp::spin(node);
14 }
15 catch (const std::exception& e)
16 {
17 std::cerr << std::string("::Exception::") << e.what();
18 }
19
20 return 0;
21}