Generic Publishers 的实现核心思想是:绕过编译时类型检查,通过运行时动态加载和序列化来实现类型无关的发布。具体机制如下:
核心架构
1. 非模板类设计
GenericPublisher 不是模板类,直接继承自 PublisherBase。因为编译时不知道类型,所以无法使用模板参数化。
关键成员:
cpp
std::shared_ptr<rcpputils::SharedLibrary> ts_lib_; // 持有类型支持库,确保不被卸载 2. 运行时类型支持加载
通过 typesupport_helpers 实现:
步骤1:解析类型字符串
- extract_type_identifier() 从
"std_msgs/msg/String"中提取出(package_name="std_msgs", middle_module="msg", type_name="String")
步骤2:定位并加载类型支持库
- get_typesupport_library_path() 在 ament 索引中查找库路径,如
libstd_msgs__rosidl_typesupport_cpp.so - get_typesupport_library() 使用
rcpputils::SharedLibrary动态加载
步骤3:获取类型支持句柄
- get_message_typesupport_handle() 通过符号查找获取类型支持句柄
- 符号命名规则:
{typesupport_identifier}__get_message_type_support_handle__{package_name}__{middle_module}__{type_name}
3. 序列化消息传递
GenericPublisher::publish() 直接发布序列化消息:
cpp
rcl_publish_serialized_message(get_publisher_handle().get(), &message.get_rcl_serialized_message(), NULL); SerializedMessage 封装了 rcl_serialized_message_t,提供 RAII 语义管理二进制数据。
4. 零拷贝优化(可选)
publish_as_loaned_msg() 提供了借出消息机制:
- borrow_loaned_message():从中间件借用消息内存
- deserialize_message():将序列化数据反序列化到借出的内存
- publish_loaned_message():直接发布借出的消息,避免拷贝
工作流程
用户指定类型字符串 (如 "std_msgs/msg/String")
↓
extract_type_identifier() 解析出 package/msg/type
↓
get_typesupport_library_path() 找到 .so 文件路径
↓
SharedLibrary 动态加载库
↓
get_message_typesupport_handle() 通过符号查找获取类型句柄
↓
GenericPublisher 构造时使用类型句柄初始化 PublisherBase
↓
publish() 时直接发送 SerializedMessage 二进制数据 限制
- 不支持进程内通信:文档明确说明不支持 intra-process handling,因为类型在编译时未知
- 性能开销:动态加载库和序列化/反序列化都有额外成本
- 类型安全缺失:编译器无法检查类型匹配,错误只能在运行时发现
这个设计巧妙地利用了 ROS 2 的类型支持系统,通过动态链接库和序列化机制,实现了编译时类型未知的情况下的消息发布。