迁移 C++ 包参考
本页介绍如何将 C++ 软件包的部分内容从 ROS 1 迁移到 ROS 2。 如果这是您第一次迁移 C++ 软件包,请先阅读 C++ 迁移示例。 然后,在迁移您自己的软件包时使用本页作为参考。
构建工具
ROS 2 使用命令行工具 colcon 来构建和安装一组软件包,而不是使用 catkin_make
、catkin_make_isolated
或 catkin build
。
请参阅 初学者教程 开始使用 colcon
。
更新您的 CMakeLists.txt
以使用 ament_cmake
ROS 2 C++ 软件包使用 CMake 和 ament_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_DEPENDS
和 DEPENDS
参数为下游用户完成的。
在 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”中:
在“rosidl_default_generators”上添加“<buildtool_depend>” (example)
<buildtool_depend>rosidl_default_generators</buildtool_depend>
在“rosidl_default_runtime”上添加“<exec_depend>” (example)
<exec_depend>rosidl_default_runtime</exec_depend>
添加一个
<member_of_group>
标签,其组名为rosidl_interface_packages
(example)<member_of_group>rosidl_interface_packages</member_of_group>
在您的 CMakeLists.txt
中,将 add_message_files
、add_service_files
和 generate_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 消息、服务和操作的命名空间在包名称后使用子命名空间(分别为 msg
、srv
或 action
)。
因此,包含看起来像:#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
而不是 typedef
。
using
能够在模板逻辑中更好地工作。
有关详细信息,请参阅此处 <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