关于内部接口
ROS 2 的内部接口是公开的 C API,面向正在创建客户端库或添加新底层中间件的开发者,而非普通 ROS 用户。 ROS 客户端库提供大多数 ROS 用户熟悉的面向用户的 API,并有多种编程语言版本。
内部 API 架构概述
Section titled “内部 API 架构概述”有两个主要的内部接口:
- ROS 中间件接口(
rmwAPI) - ROS 客户端库接口(
rclAPI)
rmw API 是 ROS 2 软件栈与底层中间件实现之间的接口。
ROS 2 使用的底层中间件是 DDS 或 RTPS 实现,负责发现、发布和订阅机制、服务的请求-回复机制以及消息类型的序列化。
rcl API 是一个更高一层的 API,用于实现客户端库,不直接接触中间件实现,而是通过 ROS 中间件接口(rmw API)这一抽象层来访问。

如图所示,这些 API 是分层的,因此典型的 ROS 用户会使用客户端库 API(例如 rclcpp)来编写代码(可执行文件或库)。
客户端库(例如 rclcpp)的实现使用 rcl 接口,该接口提供对 ROS 图(ROS graph)和图事件的访问。
rcl 实现反过来使用 rmw API 来访问 ROS 图。
rcl 实现旨在为各种客户端库提供通用 ROS 概念和工具的共享实现,同时对底层中间件保持无关性。
rmw 接口则定义了支持 ROS 客户端库所需的最小中间件功能集。
最后,rmw API 的实现由中间件特定的包提供,例如 rmw_fastrtps_cpp,其库是针对厂商特定的 DDS 接口和类型编译的。
在上图中还有一个标记为 ros_to_dds 的方框,该方框表示一类可能的包,它们允许用户使用 ROS 的等效项来访问 DDS 厂商特定的对象和设置。
此抽象接口的目标之一是将 ROS 用户空间代码与底层中间件完全隔离,使切换 DDS 厂商甚至中间件技术对用户代码的影响降到最低。
不过,有时直接深入底层实现并手动调整设置也是有用的,尽管这可能带来一些副作用。
通过要求使用这些包来访问底层 DDS 厂商的对象,可以避免在常规接口中暴露厂商特定的符号和头文件。
这也便于通过检查代码依赖关系来判断哪些部分可能破坏了厂商可移植性——只需查看是否使用了 ros_to_dds 包即可。
类型特定接口
Section titled “类型特定接口”API 的某些部分必然与所交换的消息类型相关,例如发布消息或订阅话题(topic),因此需要为每种消息类型生成代码。
以下图表展示了从用户定义的 rosidl 文件(例如 .msg 文件)到用户和系统用于执行类型特定功能的类型特定代码的路径:

图:“静态”类型支持生成的流程图,从 rosidl 文件到面向用户的代码。
图的右侧展示了 .msg 文件如何直接传递给特定语言的代码生成器,例如 rosidl_generator_cpp 或 rosidl_generator_py。
这些生成器负责创建相应的代码,用户通过包含(或导入)这些代码来使用 .msg 文件中定义的消息的内存表示。
例如,考虑消息 std_msgs/String,用户可能会在 C++ 中使用语句 #include <std_msgs/msg/string.hpp> 来使用此文件,或者在 Python 中使用语句 from std_msgs.msg import String。
这些语句之所以有效,正是因为这些特定语言(但与中间件无关)的生成器包生成了相应的文件。
另外,.msg 文件用于为每种类型生成类型支持代码。
在此上下文中,类型支持是指特定于给定类型的元数据或函数,系统利用它们为给定类型执行特定任务。
给定消息的类型支持可能包括消息中每个字段的名称和类型列表等内容。
它还可能包含对可以为该类型执行特定任务的代码的引用,例如发布消息。
静态类型支持
Section titled “静态类型支持”当类型支持引用的代码为特定消息类型执行特定功能时,该代码有时需要执行中间件相关的工作。
例如,考虑类型特定的发布函数:当使用”厂商 A”时,该函数需要调用”厂商 A”的一些 API;但当使用”厂商 B”时,则需要调用”厂商 B”的 API。
为了支持中间件厂商特定的代码,用户定义的 .msg 文件可能会触发厂商特定代码的生成。
这些厂商特定的代码仍然通过类型支持抽象对用户隐藏,类似于 C++ 中的”私有实现”(Private Implementation,Pimpl)模式。
DDS 的静态类型支持
Section titled “DDS 的静态类型支持”对于基于 DDS 的中间件厂商,特别是那些基于 OMG IDL 文件(.idl 文件)生成代码的厂商,用户定义的 rosidl 文件(.msg 文件)将被转换为等效的 OMG IDL 文件(.idl 文件)。
然后从这些 OMG IDL 文件创建厂商特定的代码,并在给定类型的类型支持引用的类型特定函数中使用。
上图左侧展示了这一点:.msg 文件被 rosidl_dds 包消费以生成 .idl 文件,然后这些 .idl 文件被提供给特定语言和 DDS 厂商特定的类型支持生成包。
例如,考虑 Fast DDS 实现,它有一个名为 rosidl_typesupport_fastrtps_cpp 的包。
此包负责生成代码,处理诸如将 C++ 消息对象转换为可在网络上传输的序列化字节缓冲区等操作。
此代码虽然特定于 Fast DDS,但由于类型支持代码中的抽象层,不会暴露给用户。
动态类型支持
Section titled “动态类型支持”实现类型支持的另一种方式是使用通用函数来处理诸如发布到话题等操作,而非为每种消息类型生成单独的函数。 为此,通用函数需要一些关于所发布消息类型的元信息,例如字段名称和类型按它们在消息类型中出现的顺序排列的列表。 发布消息时,只需调用通用发布函数,传入要发布的消息以及包含消息类型必要元数据的结构即可。 这被称为”动态”类型支持,与需要为每种类型生成函数版本的”静态”类型支持相对。

图:“动态”类型支持生成的流程图,从 rosidl 文件到面向用户的代码。
上图展示了从用户定义的 rosidl 文件到生成的面向用户代码的流程。
它与静态类型支持的图表非常相似,区别仅在于类型支持的生成方式(即图的左侧部分)。
在动态类型支持中,.msg 文件直接转换为面向用户的代码。
此代码也与中间件无关,因为它仅包含有关消息的元信息。
实际执行工作的函数(例如发布到话题)对于消息类型是通用的,会根据需要调用中间件特定的 API。
请注意,与静态类型支持中由 DDS 厂商特定的包提供类型支持代码不同,此方法为每种语言提供了一个与中间件无关的包,例如 rosidl_typesupport_introspection_c 和 rosidl_typesupport_introspection_cpp。
包名称中的 introspection 部分指的是使用为消息类型生成的元数据来内省任何消息实例的能力。
这是实现诸如”发布到话题”等函数通用版本的基本能力。
此方法的优势在于,所有生成的代码都与中间件无关,这意味着只要底层中间件支持动态类型支持,代码即可在不同的中间件实现间复用。 同时还减少了生成的代码量,从而减少了编译时间和代码大小。
然而,动态类型支持要求底层中间件支持类似形式的动态类型支持。 在 DDS 的情况下,DDS-XTypes 标准允许使用元信息而非生成的代码来发布消息。 要支持动态类型支持,底层中间件需要 DDS-XTypes 或类似功能。 此外,这种类型支持方法通常比静态类型支持更慢。 静态类型支持中的类型特定生成代码可以编写得更加高效,因为它不需要遍历消息类型的元数据来执行序列化等操作。
rcl 仓库
Section titled “rcl 仓库”ROS 客户端库接口(rcl API)可供客户端库(例如 rclc、rclcpp、rclpy 等)使用,从而避免重复实现逻辑和功能。
通过重用 rcl API,客户端库可以更小且彼此更加一致。
客户端库的某些部分被有意排除在 rcl API 之外,因为这些部分应该使用各语言的惯用方式来实现。
一个很好的例子是执行模型,rcl 完全不涉及。
相反,客户端库应提供语言惯用的解决方案,如 C 中的 pthreads、C++11 中的 std::thread 和 Python 中的 threading.Thread。
通常,rcl 接口提供的函数既不依赖于特定编程语言模式,也不依赖于特定消息类型。
rcl API 位于 GitHub 上的 ros2/rcl 仓库中,包含作为 C 头文件的接口。
rcl 的 C 实现由同一仓库中的 rcl 包提供。
此实现通过使用 rmw 和 rosidl API 来避免与中间件的直接接触。
有关 rcl API 的完整定义,请参阅 rcl 文档。
rmw 仓库
Section titled “rmw 仓库”ROS 中间件接口(rmw API)定义了构建 ROS 所需的最小中间件原语能力集。
中间件实现方必须实现此接口,才能在其上运行完整的 ROS 栈。
目前大多数中间件实现都是针对不同 DDS 厂商的。
rmw API 位于 ros2/rmw 仓库中。
rmw 包包含定义接口的 C 头文件,其实现由不同 DDS 厂商的各种 rmw 实现包提供。
有关 rmw API 的定义,请参阅 rmw 文档。
有关 ROS 2 如何与不同中间件实现集成的更实用的深入概述,请参阅 中间件实现教程。
rosidl 仓库
Section titled “rosidl 仓库”rosidl API 由一些与消息相关的静态函数和类型组成,同时定义了在不同语言中应为消息生成哪些代码。
API 中指定的消息代码生成是特定语言的,但可能会(也可能不会)复用为其他语言生成的代码。
API 中指定的生成代码包括消息数据结构、构造和析构函数等内容。
API 还提供了获取消息类型的类型支持结构的方法,该结构在发布或订阅该消息类型的话题时使用。
有几个仓库在 rosidl API 和实现中发挥作用。
rosidl 仓库位于 GitHub 上的 ros2/rosidl,定义了消息 IDL 语法(即 .msg 文件、.srv 文件等的语法),并包含用于解析文件的包、提供 CMake 基础设施以从消息生成代码的包、生成与实现无关的代码(头文件和源文件)的包,以及建立默认生成器集合的包。
该仓库包含以下包:
rosidl_cmake:提供用于从rosidl文件(例如.msg文件、.srv文件等)生成代码的 CMake 函数和模块。rosidl_default_generators:定义默认生成器列表,确保它们作为依赖项安装,但也可以使用其他注入的生成器。rosidl_generator_c:提供为rosidl文件生成 C 头文件(.h)的工具。rosidl_generator_cpp:提供为rosidl文件生成 C++ 头文件(.hpp)的工具。rosidl_generator_py:提供为rosidl文件生成 Python 模块的工具。rosidl_parser:提供用于解析rosidl文件的 Python API。
其他语言的生成器,例如 rosidl_generator_java,托管在外部(不同的仓库中),但将使用与上述生成器相同的机制将自身”注册”为 rosidl 生成器。
除了上述用于解析 rosidl 文件和生成头文件的包之外,rosidl 仓库还包含与文件中定义的消息类型的”类型支持”相关的包。
类型支持是指解释和操作特定类型的 ROS 消息实例所表示的信息的能力(例如发布消息)。
类型支持可以由编译时生成的代码提供,也可以基于 rosidl 文件(例如 .msg 或 .srv 文件)的内容,通过在运行时内省数据以编程方式实现。
在后一种情况下,类型支持通过对消息的运行时解释来实现,因此 ROS 2 生成的消息代码可以与 rmw 实现无关。
通过内省数据提供此类型支持的包有:
rosidl_typesupport_introspection_c:提供生成 C 代码以支持rosidl消息数据类型的工具。rosidl_typesupport_introspection_cpp:提供生成 C++ 代码以支持rosidl消息数据类型的工具。
当类型支持需要在编译时生成(而非以编程方式实现)时,需要使用特定于 rmw 实现的包。 这是因为通常特定的 rmw 实现要求数据以 DDS 厂商特定的方式存储和操作,以便 DDS 实现能够使用它。 有关更多详细信息,请参阅上面的 类型特定接口 部分。
rosidl_generator_cpp package
Section titled “rosidl_generator_cpp package”此包负责生成表示 IDL 的 C++ 结构。 它还生成便于用户使用并提升 C++ 语言互操作性的工具。
消息成员名称
Section titled “消息成员名称”可以使用 rosidl_generator_traits::MessageTraits 在编译时检索消息中成员的名称。
#include <builtin_interfaces/msg/time.hpp>
// ...
using MessageTraitsTime = rosidl_generator_traits::MessageTraits<builtin_interfaces::msg::Time>;std::array<std::string_view, 2> member_names = MessageTraitsTime::member_names; // Returns {"sec", "nanosec"}消息成员元编程
Section titled “消息成员元编程”为了在 C++26 反射之前促进元编程技术,代码生成中添加了 as_tuple_ref 工具。
它以 tuple 的形式返回对结构体各成员的引用。
这样无需通过成员名称即可读写消息内容。
#include <tuple>#include <builtin_interfaces/msg/time.hpp>
// ...
// Increment both members using ``std::apply`` and a fold-expressionbuiltin_interfaces::msg::Time time;std::apply([](auto & ... member) { ((member += 1), ...); }, as_tuple_ref(msg));警告:使用此技术可以在不被检测的情况下交换相同类型的成员。如果接口可能更改,请谨慎使用。
as_tuple_ref函数不会延长传递给它的消息的生命周期。程序员有责任确保消息对象的生命周期长于这些引用的使用。
rcutils 仓库
Section titled “rcutils 仓库”ROS 2 C 工具库(rcutils)是一个 C API,由贯穿整个 ROS 2 代码库的宏、函数和数据结构组成。
这些主要用于错误处理、命令行参数解析和日志记录,这些功能不特定于客户端或中间件层,可以由两者共享。
rcutils API 和实现位于 GitHub 上的 ros2/rcutils 仓库中,包含作为 C 头文件的接口。
有关 rcutils API 的完整定义,请参阅 rcutils 文档。