迁移 C++ 包参考

本页介绍如何将 C++ 软件包的部分内容从 ROS 1 迁移到 ROS 2。 如果这是您第一次迁移 C++ 软件包,请先阅读 C++ 迁移示例。 然后,在迁移您自己的软件包时使用本页作为参考。

构建工具

ROS 2 使用命令行工具 colcon 来构建和安装一组软件包,而不是使用 catkin_makecatkin_make_isolatedcatkin build。 请参阅 初学者教程 开始使用 colcon

更新您的 CMakeLists.txt 以使用 ament_cmake

ROS 2 C++ 软件包使用 CMakeament_cmake 提供的便捷函数。

应用以下更改以使用 ament_cmake 而不是 catkin

需要较新版本的 CMake

ROS 2 依赖比 ROS 1 使用的较新版本的 CMake。 在 REP 2000 中找到您想要支持的 ROS 发行版使用的 CMake 的最低版本,并在 CMakeLists.txt 的顶部使用该版本。 例如,3.14.4 是 ROS Humble 的最低推荐支持。

cmake_minimum_required(VERSION 3.14.4)

将构建类型设置为 ament_cmake

从“package.xml”中删除对“catkin”的任何依赖项

# Remove this!
<buildtool_depend>catkin</buildtool_depend>

在“ament_cmake_ros”上添加一个新的依赖项(`示例<https://github.com/ros2/geometry2/blob/d85102217f692746abea8546c8e41f0abc95c8b8/tf2/package.xml#L25>`__):

<buildtool_depend>ament_cmake_ros</buildtool_depend>

如果您的 package.xml 中还没有 <export> 部分,请添加一个。 将 <build_type> 设置为 ament_cmake (示例)

<export>
   <build_type>ament_cmake</build_type>
</export>

添加对“ament_package()”的调用

在“CMakeLists.txt”底部插入对“ament_package()”的调用(示例<https://github.com/ros2/geometry2/blob/d85102217f692746abea8546c8e41f0abc95c8b8/tf2/CMakeLists.txt#L127>`__)

# Add this to the bottom of your CMakeLists.txt
ament_package()

更新 find_package() 调用

find_package(catkin COMPONENTS ...) 调用替换为单独的 find_package() 调用(示例):

例如,更改此内容:

find_package(catkin REQUIRED COMPONENTS foo bar std_msgs)
find_package(baz REQUIRED)

对此:

find_package(ament_cmake_ros REQUIRED)
find_package(foo REQUIRED)
find_package(bar REQUIRED)
find_package(std_msgs REQUIRED)
find_package(baz REQUIRED)

使用现代 CMake 目标

最好使用每个目标的 CMake 函数,以便您的包可以导出现代 CMake 目标。

如果您的“CMakeLists.txt”使用“include_directories()”,则删除这些调用。

# Delete calls to include_directories like this one!
include_directories(include ${catkin_INCLUDE_DIRS})

为包中的每个库添加一个调用“target_include_directories()”(“示例<https://github.com/ros2/geometry2/blob/d85102217f692746abea8546c8e41f0abc95c8b8/tf2/CMakeLists.txt#L24-L26>`__)。

target_include_directories(my_library PUBLIC
   "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
   "$<INSTALL_INTERFACE:include/${PROJECT_NAME}>")

将所有“target_link_libraries()”调用更改为使用现代 CMake 目标。 例如,如果您的 ROS 1 中的包使用像这样的旧式标准 CMake 变量。

target_link_libraries(my_library ${catkin_LIBRARIES} ${baz_LIBRARIES})

然后将其更改为使用特定的现代 CMake 目标。 如果您所依赖的包是消息包(例如“std_msgs”),请使用“${package_name_TARGETS}”。

target_link_libraries(my_library PUBLIC foo::foo bar::bar ${std_msgs_TARGETS} baz::baz)

根据库如何使用依赖项选择“PUBLIC”或“PRIVATE”(例如<https://github.com/ros2/geometry2/blob/d85102217f692746abea8546c8e41f0abc95c8b8/tf2/CMakeLists.txt#L27-L31>`__)。

  • 如果下游用户需要依赖项,则使用“PUBLIC”,例如,库的公共 API 使用它。

  • 如果依赖项仅在库内部使用,则使用“PRIVATE”。

catkin_package() 替换为各种 ament_cmake 调用

想象一下您的 CMakeLists.txt 有对 catkin_package 的调用,如下所示:

catkin_package(
    INCLUDE_DIRS include
    LIBRARIES my_library
    CATKIN_DEPENDS foo bar std_msgs
    DEPENDS baz
)

install(TARGETS my_library
   ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
   LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
   RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
)

替换 catkin_package(INCLUDE_DIRS ...)

如果您使用了现代 CMake 目标和 target_include_directories(),则无需执行任何进一步的操作。

下游用户将通过依赖您的现代 CMake 目标来获取包含目录。

替换 catkin_package(LIBRARIES ...)

使用 ament_export_targets()install(TARGETS ... EXPORT ...) 替换 LIBRARIES 参数。

安装“my_library”目标时使用“EXPORT”关键字(`示例<https://github.com/ros2/geometry2/blob/d85102217f692746abea8546c8e41f0abc95c8b8/tf2/CMakeLists.txt#L37-L41>`__)。

install(TARGETS my_library EXPORT export_my_package
   ARCHIVE DESTINATION lib
   LIBRARY DESTINATION lib
   RUNTIME DESTINATION bin
)

以上是库目标的良好默认值。 如果您的包使用了不同的“CATKIN_*_DESTINATION”变量,请按如下方式转换它们:

catkin

ament_cmake

CATKIN_GLOBAL_BIN_DESTINATION

bin

CATKIN_GLOBAL_INCLUDE_DESTINATION

include

CATKIN_GLOBAL_LIB_DESTINATION

lib

CATKIN_GLOBAL_LIBEXEC_DESTINATION

lib

CATKIN_GLOBAL_SHARE_DESTINATION

share

CATKIN_PACKAGE_BIN_DESTINATION

lib/${PROJECT_NAME}

CATKIN_PACKAGE_INCLUDE_DESTINATION

include/${PROJECT_NAME}

CATKIN_PACKAGE_LIB_DESTINATION

lib

CATKIN_PACKAGE_SHARE_DESTINATION

share/${PROJECT_NAME}

添加对“ament_export_targets()”的调用,并使用与“EXPORT”关键字相同的名称(`示例<https://github.com/ros2/geometry2/blob/d85102217f692746abea8546c8e41f0abc95c8b8/tf2/CMakeLists.txt#L124-L125>`__)。

ament_export_targets(export_my_package)

替换 catkin_package(CATKIN_DEPENDS .. DEPENDS ..)

您的软件包的用户必须找到您的软件包的公共 API 所使用的 find_package() 依赖项。 在 ROS 1 中,这是使用 CATKIN_DEPENDSDEPENDS 参数为下游用户完成的。 在 ROS 2 中使用 ``ament_export_dependencies <https://github.com/ament/ament_cmake/blob/rolling/ament_cmake_export_dependencies/cmake/ament_export_dependencies.cmake>`__ 执行此操作。

ament_export_dependencies(
   foo
   bar
   std_msgs
   baz
)

生成消息

如果您的包包含 C++ 代码和 ROS 消息、服务或操作定义,请考虑将其拆分为两个包:

  • 仅包含 ROS 消息、服务和/或操作定义的包

  • 包含 C++ 代码的包

将以下依赖项添加到包含 ROS 消息的包的“package.xml”中:

  1. 在“rosidl_default_generators”上添加“<buildtool_depend>” (example)

    <buildtool_depend>rosidl_default_generators</buildtool_depend>
    
  2. 在“rosidl_default_runtime”上添加“<exec_depend>” (example)

    <exec_depend>rosidl_default_runtime</exec_depend>
    
  3. 添加一个 <member_of_group> 标签,其组名为 rosidl_interface_packages (example)

    <member_of_group>rosidl_interface_packages</member_of_group>
    

在您的 CMakeLists.txt 中,将 add_message_filesadd_service_filesgenerate_messages 的调用替换为 rosidl_generate_interfaces <https://github.com/ros2/rosidl/blob/rolling/rosidl_cmake/cmake/rosidl_generate_interfaces.cmake>`__。 由于 `this bug <https://github.com/ros2/rosidl_typesupport/issues/120>`__,第一个参数必须是 ``${PROJECT_NAME}

例如,如果您的 ROS 1 包如下所示:

add_message_files(DIRECTORY msg FILES FooBar.msg Baz.msg)
add_service_files(DIRECTORY srv FILES Ping.srv)

add_action_files(DIRECTORY action FILES DoPong.action)
generate_messages(
   DEPENDENCIES actionlib_msgs std_msgs geometry_msgs
)

然后将其更改为 (example)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/FooBar.msg"
  "msg/Baz.msg"
  "srv/Ping.srv"
  "action/DoPong.action"
  DEPENDENCIES actionlib_msgs std_msgs geometry_msgs
)

删除对 devel 空间的引用

删除对 devel 空间 的任何引用,例如 CATKIN_DEVEL_PREFIX

ROS 2 中没有与 devel 空间 等效的引用。

单元测试

如果您的包使用 gtest,则:

  • CATKIN_ENABLE_TESTING 替换为 BUILD_TESTING

  • catkin_add_gtest 替换为 ament_add_gtest

  • ament_cmake_gtest 添加 find_package(),而不是 GTest

例如,如果您的 ROS 1 包添加了如下测试:

if (CATKIN_ENABLE_TESTING)
  find_package(GTest REQUIRED)
  include_directories(${GTEST_INCLUDE_DIRS})
  catkin_add_gtest(my_test src/test/some_test.cpp)
  target_link_libraries(my_test
    # ...
    ${GTEST_LIBRARIES})
endif()

然后将其改为:

if (BUILD_TESTING)
  find_package(ament_cmake_gtest REQUIRED)
  ament_add_gtest(my_test src/test/test_something.cpp)
  target_link_libraries(my_test
    #...
   )
endif()

<test_depend>ament_cmake_gtest</test_depend> 添加到你的 package.xml 中(示例)。

<test_depend>ament_cmake_gtest</test_depend>

Linters

ROS 2 代码 style guide 与 ROS 1 不同。

如果您选择遵循 ROS 2 样式指南,则通过在 if(BUILD_TESTING) 块中添加以下行来启用自动 linter 测试:

if(BUILD_TESTING)
   find_package(ament_lint_auto REQUIRED)
   ament_lint_auto_find_test_dependencies()
   # ...
endif()

将以下依赖项添加到您的“package.xml”:

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>

更新源代码

消息、服务和操作

ROS 2 消息、服务和操作的命名空间在包名称后使用子命名空间(分别为 msgsrvaction)。 因此,包含看起来像:#include <my_interfaces/msg/my_message.hpp>。 然后,C++ 类型被命名为:my_interfaces::msg::MyMessage

共享指针类型作为消息结构中的 typedef 提供:my_interfaces::msg::MyMessage::SharedPtr 以及 my_interfaces::msg::MyMessage::ConstSharedPtr

有关更多详细信息,请参阅有关“生成的 C++ 接口 <https://design.ros2.org/articles/generated_interfaces_cpp.html>”的文章。

迁移需要更改包含内容:

  • 在包名称和消息数据类型之间插入子文件夹“msg”

  • 将包含的文件名从 CamelCase 更改为下划线分隔

  • 从“.h”更改为“.hpp”

// ROS 1 style is in comments, ROS 2 follows, uncommented.
// # include <geometry_msgs/PointStamped.h>
#include <geometry_msgs/msg/point_stamped.hpp>

// geometry_msgs::PointStamped point_stamped;
geometry_msgs::msg::PointStamped point_stamped;

迁移需要代码将“msg”命名空间插入到所有实例中。

服务对象的使用

ROS 2 中的服务回调没有布尔返回值。 建议抛出异常,而不是在失败时返回 false。

// ROS 1 style is in comments, ROS 2 follows, uncommented.
// #include "nav_msgs/GetMap.h"
#include "nav_msgs/srv/get_map.hpp"

// bool service_callback(
//   nav_msgs::GetMap::Request & request,
//   nav_msgs::GetMap::Response & response)
void service_callback(
  const std::shared_ptr<nav_msgs::srv::GetMap::Request> request,
  std::shared_ptr<nav_msgs::srv::GetMap::Response> response)
{
  // ...
  // return true;  // or false for failure
}

ros::Time 的用法

对于 ros::Time 的用法:

  • 将所有 ros::Time 实例替换为 rclcpp::Time

  • 如果您的消息或代码使用了 std_msgs::Time:

  • 将所有 std_msgs::Time 实例转换为builtin_interfaces::msg::Time

  • 将所有 #include "std_msgs/time.h 转换为 #include "builtin_interfaces/msg/time.hpp"

  • 将所有使用 std_msgs::Time 字段 nsec 的实例转换为builtin_interfaces::msg::Time 字段 nanosec

用法ros::Rate

有一个等效类型 rclcpp::Rate 对象,它基本上是 ros::Rate 的替代品。

Boost

Boost 以前提供的许多功能已集成到 C++ 标准库中。

因此,我们希望利用新的核心功能,并尽可能避免对 boost 的依赖。

共享指针

要将共享指针从 boost 切换到标准 C++,请替换以下实例:

  • #include <boost/shared_ptr.hpp> 替换为 #include <memory>

  • boost::shared_ptr 替换为 std::shared_ptr

可能还存在您想要转换的变体,例如 weak_ptr

此外,建议使用 using 而不是 typedefusing 能够在模板逻辑中更好地工作。 有关详细信息,请参阅此处 <https://stackoverflow.com/questions/10747810/what-is-the-difference-between-typedef-and-using-in-c11>`__

线程/互斥锁

ROS 代码库中使用的 boost 的另一个常见部分是 boost::thread 中的互斥锁。

  • boost::mutex::scoped_lock 替换为 std::unique_lock<std::mutex>

  • boost::mutex 替换为 std::mutex

  • #include <boost/thread/mutex.hpp> 替换为 #include <mutex>

无序映射

替换:

  • #include <boost/unordered_map.hpp> 替换为 #include <unordered_map>

  • boost::unordered_map 替换为 std::unordered_map

函数

替换:

  • #include <boost/function.hpp> 替换为 #include < functional>

  • boost::function 替换为 std::function