Robot Operating System——创建动态链接文件项目的步骤


《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在通信。
在这里插入图片描述

参考资料

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值