在 《Robot Operating System——创建可执行文件项目的步骤》一文中,我们熟悉了如何创建一个ROS2的可执行文件。但是在对之前诸多案例分析后,我们发现动态链接库的形式更加普遍。这是因为一个Node可以以一个动态链接库的形式存在。这样我们一个进程便可以灵活地加载多个动态链接库,从而实现在一个进程中运行多个Node。
初始化环境
由于编译和运行需要依赖很多ROS2的库,所以我们需要初始化下当前的环境。
source /opt/ros/jazzy/setup.bash
关于环境的安装可以参见《Robot Operating System——Ubuntu上以二进制形式安装环境》。
创建Package
ros2 pkg create --build-type ament_cmake --license Apache-2.0 two_node_dynamic_library
可以看到ros2帮我们创建好了相关的目录结构
代码
我们准备在一个动态链接库中包含两个Node ,然后使用ROS2 Demo中的manual_composition来加载并测试它们。
在include/two_node_dynamic_library下新建visibility_control.h文件,填入以下内容
// Copyright 2016 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef DEMO_NODES_CPP__VISIBILITY_CONTROL_H_
#define DEMO_NODES_CPP__VISIBILITY_CONTROL_H_
#ifdef __cplusplus
extern "C"
{
#endif
// This logic was borrowed (then namespaced) from the examples on the gcc wiki:
// https://gcc.gnu.org/wiki/Visibility
#if defined _WIN32 || defined __CYGWIN__
#ifdef __GNUC__
#define DEMO_NODES_CPP_EXPORT __attribute__ ((dllexport))
#define DEMO_NODES_CPP_IMPORT __attribute__ ((dllimport))
#else
#define DEMO_NODES_CPP_EXPORT __declspec(dllexport)
#define DEMO_NODES_CPP_IMPORT __declspec(dllimport)
#endif
#ifdef DEMO_NODES_CPP_BUILDING_DLL
#define DEMO_NODES_CPP_PUBLIC DEMO_NODES_CPP_EXPORT
#else
#define DEMO_NODES_CPP_PUBLIC DEMO_NODES_CPP_IMPORT
#endif
#define DEMO_NODES_CPP_PUBLIC_TYPE DEMO_NODES_CPP_PUBLIC
#define DEMO_NODES_CPP_LOCAL
#else
#define DEMO_NODES_CPP_EXPORT __attribute__ ((visibility("default")))
#define DEMO_NODES_CPP_IMPORT
#if __GNUC__ >= 4
#define DEMO_NODES_CPP_PUBLIC __attribute__ ((visibility("default")))
#define DEMO_NODES_CPP_LOCAL __attribute__ ((visibility("hidden")))
#else
#define DEMO_NODES_CPP_PUBLIC
#define DEMO_NODES_CPP_LOCAL
#endif
#define DEMO_NODES_CPP_PUBLIC_TYPE
#endif
#ifdef __cplusplus
}
#endif
#endif // DEMO_NODES_CPP__VISIBILITY_CONTROL_H_
在src目录下,我们新建一个talker.cpp,填入以下的代码
// Copyright 2014 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <chrono>
#include <cstdio>
#include <memory>
#include <utility>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_components/register_node_macro.hpp"
#include "std_msgs/msg/string.hpp"
#include "two_node_dynamic_library/visibility_control.h"
using namespace std::chrono_literals;
namespace two_node_dynamic_library
{
// Create a Talker class that subclasses the generic rclcpp::Node base class.
// The main function below will instantiate the class as a ROS node.
class Talker : public rclcpp::Node
{
public:
DEMO_NODES_CPP_PUBLIC
explicit Talker(const rclcpp::NodeOptions & options)
: Node("talker", options)
{
// Create a function for when messages are to be sent.
setvbuf(stdout, NULL, _IONBF, BUFSIZ);
auto publish_message =
[this]() -> void
{
msg_ = std::make_unique<std_msgs::msg::String>();
msg_->data = "Hello World: " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", msg_->data.c_str());
// Put the message into a queue to be processed by the middleware.
// This call is non-blocking.
pub_->publish(std::move(msg_));
};
// Create a publisher with a custom Quality of Service profile.
// Uniform initialization is suggested so it can be trivially changed to
// rclcpp::KeepAll{} if the user wishes.
// (rclcpp::KeepLast(7) -> rclcpp::KeepAll() fails to compile)
rclcpp::QoS qos(rclcpp::KeepLast{7});
pub_ = this->create_publisher<std_msgs::msg::String>("chatter", qos);
// Use a timer to schedule periodic message publishing.
timer_ = this->create_wall_timer(1s, publish_message);
}
private:
size_t count_ = 1;
std::unique_ptr<std_msgs::msg::String> msg_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
rclcpp::TimerBase::SharedPtr timer_;
};
} // namespace two_node_dynamic_library
RCLCPP_COMPONENTS_REGISTER_NODE(two_node_dynamic_library::Talker)
再新建一个文件listener.cpp,填入以下内容
// Copyright 2014 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_components/register_node_macro.hpp"
#include "std_msgs/msg/string.hpp"
#include "two_node_dynamic_library/visibility_control.h"
namespace two_node_dynamic_library
{
// Create a Listener class that subclasses the generic rclcpp::Node base class.
// The main function below will instantiate the class as a ROS node.
class Listener : public rclcpp::Node
{
public:
DEMO_NODES_CPP_PUBLIC
explicit Listener(const rclcpp::NodeOptions & options)
: Node("listener", options)
{
// Create a callback function for when messages are received.
// Variations of this function also exist using, for example UniquePtr for zero-copy transport.
setvbuf(stdout, NULL, _IONBF, BUFSIZ);
auto callback =
[this](std_msgs::msg::String::ConstSharedPtr msg) -> void
{
RCLCPP_INFO(this->get_logger(), "I heard: [%s]", msg->data.c_str());
};
// Create a subscription to the topic which can be matched with one or more compatible ROS
// publishers.
// Note that not all publishers on the same topic with the same type will be compatible:
// they must have compatible Quality of Service policies.
sub_ = create_subscription<std_msgs::msg::String>("chatter", 10, callback);
}
private:
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;
};
} // namespace two_node_dynamic_library
RCLCPP_COMPONENTS_REGISTER_NODE(two_node_dynamic_library::Listener)
此时目录结构如下
添加依赖(package.xml)
package.xml 是 ROS 2 包的元数据文件,定义了包的基本信息和依赖关系。
打开two_node_pipeline/package.xml,添加代码中include头文件(#include “rclcpp/rclcpp.hpp”、#include "std_msgs/msg/int32.hpp"和#include “rclcpp_components/register_node_macro.hpp”)所依赖的其他库。
<build_depend>rclcpp</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>rclcpp_components</build_depend>
完整文件如下
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>two_node_dynamic_library</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="f304646673@gmail.com">fangliang</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<build_depend>rclcpp</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>rclcpp_components</build_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
修改编译描述
find_package寻找依赖库
再打开CMakeLists.txt中添加上述头文件(#include “rclcpp/rclcpp.hpp”、#include "std_msgs/msg/int32.hpp"和#include “rclcpp_components/register_node_macro.hpp”)所依赖的库
find_package(rclcpp REQUIRED)
find_package(rclcpp_components REQUIRED)
find_package(std_msgs REQUIRED)
在 CMake 中,find_package 命令用于查找并加载外部包或库。它的主要作用如下:
-
查找包:find_package 会在系统中查找指定的包或库,并加载其配置文件。这些配置文件通常包含包的路径、库文件、头文件等信息。
-
设置变量:如果找到包,find_package 会设置一些变量,这些变量可以在后续的 CMake 脚本中使用。例如,<PackageName>_FOUND 变量会被设置为 TRUE,表示找到了包。
-
导入目标:一些包会定义 CMake 导入目标(imported targets),这些目标可以直接在 target_link_libraries 等命令中使用。
指定代码路径和编译类型(动态库)
设定编译结果名称
set(DYNAMIC_LIBRARY_NAME "two_node_dynamic_library")
指定编译结果是动态库文件
# Collect all source files in this directory
file(GLOB SOURCES "src/*.cpp")
# Add the library target
add_library(${DYNAMIC_LIBRARY_NAME} SHARED ${SOURCES})
设置头文件路径
target_include_directories(${DYNAMIC_LIBRARY_NAME} PRIVATE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>")
链接依赖的库
最后将代码中用到的库(#include “rclcpp/rclcpp.hpp”、#include "std_msgs/msg/int32.hpp"和#include “rclcpp_components/register_node_macro.hpp”)和生成的动态库关联。
target_link_libraries(${DYNAMIC_LIBRARY_NAME} PRIVATE rclcpp::rclcpp ${std_msgs_TARGETS} rclcpp::rclcpp rclcpp_components::component)
编译
cd two_node_dynamic_library/
mkdir build
cd build/
cmake ..
make
测试
我们使用《Robot Operating System——初探可执行文件、动态库运行模式》一文中的manual_composition 来加载生成的动态库。
./manual_composition ~/github/ros2-examples/two_node_dynamic_library/build/libtwo_node_dynamic_library.so
可以看到,进程中有两个Node在通信。