深入解析 ROS 2 的通用订阅者:GenericSubscription
类逐行解析
概述
在 ROS 2 中,GenericSubscription
类为用户提供了一种灵活的方式来订阅序列化消息,特别是当消息类型在编译时未知时。GenericSubscription
类支持订阅动态类型的消息,并在运行时加载所需的类型支持库。本文将逐行解析 GenericSubscription
类的实现,详细解释其构造、方法及内部工作原理。
源码位置
// Copyright 2018, Bosch Software Innovations GmbH.
// Copyright 2021, Apex.AI 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 RCLCPP__GENERIC_SUBSCRIPTION_HPP_
#define RCLCPP__GENERIC_SUBSCRIPTION_HPP_
#include <functional>
#include <memory>
#include <string>
#include "rcpputils/shared_library.hpp"
#include "rclcpp/callback_group.hpp"
#include "rclcpp/macros.hpp"
#include "rclcpp/node_interfaces/node_base_interface.hpp"
#include "rclcpp/node_interfaces/node_topics_interface.hpp"
#include "rclcpp/qos.hpp"
#include "rclcpp/serialized_message.hpp"
#include "rclcpp/subscription_base.hpp"
#include "rclcpp/typesupport_helpers.hpp"
#include "rclcpp/visibility_control.hpp"
namespace rclcpp
{
/// %Subscription for serialized messages whose type is not known at compile time.
/**
* Since the type is not known at compile time, this is not a template, and the dynamic library
* containing type support information has to be identified and loaded based on the type name.
*
* It does not support intra-process handling.
*/
class GenericSubscription : public rclcpp::SubscriptionBase
{
public:
// cppcheck-suppress unknownMacro
RCLCPP_SMART_PTR_DEFINITIONS(GenericSubscription)
/// Constructor.
/**
* In order to properly subscribe to a topic, this subscription needs to be added to
* the node_topic_interface of the node passed into this constructor.
*
* \sa rclcpp::Node::create_generic_subscription() or rclcpp::create_generic_subscription() for
* creating an instance of this class and adding it to the node_topic_interface.
*
* \param node_base Pointer to parent node's NodeBaseInterface
* \param ts_lib Type support library, needs to correspond to topic_type
* \param topic_name Topic name
* \param topic_type Topic type
* \param qos %QoS settings
* \param callback Callback for new messages of serialized form
* \param options %Subscription options.
* Not all subscription options are currently respected, the only relevant options for this
* subscription are `event_callbacks`, `use_default_callbacks`, `ignore_local_publications`, and
* `%callback_group`.
*/
template<typename AllocatorT = std::allocator<void>>
GenericSubscription(
rclcpp::node_interfaces::NodeBaseInterface * node_base,
const std::shared_ptr<rcpputils::SharedLibrary> ts_lib,
const std::string & topic_name,
const std::string & topic_type,
const rclcpp::QoS & qos,
AnySubscriptionCallback<rclcpp::SerializedMessage, AllocatorT> callback,
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & options)
: SubscriptionBase(
node_base,
*rclcpp::get_message_typesupport_handle(topic_type, "rosidl_typesupport_cpp", *ts_lib),
topic_name,
options.to_rcl_subscription_options(qos),
options.event_callbacks,
options.use_default_callbacks,
DeliveredMessageKind::SERIALIZED_MESSAGE),
any_callback_(callback),
ts_lib_(ts_lib)
{
TRACETOOLS_TRACEPOINT(
rclcpp_subscription_init,
static_cast<const void *>(get_subscription_handle().get()),
static_cast<const void *>(this));
TRACETOOLS_TRACEPOINT(
rclcpp_subscription_callback_added,
static_cast<const void *>(this),
static_cast<const void *>(&any_callback_));
#ifndef TRACETOOLS_DISABLED
any_callback_.register_callback_for_tracing();
#endif
}
RCLCPP_PUBLIC
virtual ~GenericSubscription() = default;
// Same as create_serialized_message() as the subscription is to serialized_messages only
RCLCPP_PUBLIC
std::shared_ptr<void> create_message() override;
RCLCPP_PUBLIC
std::shared_ptr<rclcpp::SerializedMessage> create_serialized_message() override;
/// Cast the message to a rclcpp::SerializedMessage and call the callback.
RCLCPP_PUBLIC
void handle_message(
std::shared_ptr<void> & message, const rclcpp::MessageInfo & message_info) override;
/// Handle dispatching rclcpp::SerializedMessage to user callback.
RCLCPP_PUBLIC
void
handle_serialized_message(
const std::shared_ptr<rclcpp::SerializedMessage> & serialized_message,
const rclcpp::MessageInfo & message_info) override;
/// This function is currently not implemented.
RCLCPP_PUBLIC
void handle_loaned_message(
void * loaned_message, const rclcpp::MessageInfo & message_info) override;
// Same as return_serialized_message() as the subscription is to serialized_messages only
RCLCPP_PUBLIC
void return_message(std::shared_ptr<void> & message) override;
RCLCPP_PUBLIC
void return_serialized_message(std::shared_ptr<rclcpp::SerializedMessage> & message) override;
// DYNAMIC TYPE ==================================================================================
RCLCPP_PUBLIC
rclcpp::dynamic_typesupport::DynamicMessageType::SharedPtr get_shared_dynamic_message_type()
override;
RCLCPP_PUBLIC
rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr get_shared_dynamic_message() override;
RCLCPP_PUBLIC
rclcpp::dynamic_typesupport::DynamicSerializationSupport::SharedPtr
get_shared_dynamic_serialization_support() override;
RCLCPP_PUBLIC
rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr create_dynamic_message() override;
RCLCPP_PUBLIC
void return_dynamic_message(
rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message) override;
RCLCPP_PUBLIC
void handle_dynamic_message(
const rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message,
const rclcpp::MessageInfo & message_info) override;
private:
RCLCPP_DISABLE_COPY(GenericSubscription)
AnySubscriptionCallback<rclcpp::SerializedMessage, std::allocator<void>> any_callback_;
// The type support library should stay loaded, so it is stored in the GenericSubscription
std::shared_ptr<rcpputils::SharedLibrary> ts_lib_;
};
} // namespace rclcpp
#endif // RCLCPP__GENERIC_SUBSCRIPTION_HPP_
逐行解析
文件头部
// Copyright 2018, Bosch Software Innovations GmbH.
// Copyright 2021, Apex.AI 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.
- 解释:这部分代码是版权声明和许可证信息,表明该代码最初由 Bosch Software Innovations GmbH 和 Apex.AI Inc. 开发,并受 Apache License 2.0 约束。
头文件保护与包含的头文件
#ifndef RCLCPP__GENERIC_SUBSCRIPTION_HPP_
#define RCLCPP__GENERIC_SUBSCRIPTION_HPP_
#include <functional>
#include <memory>
#include <string>
#include "rcpputils/shared_library.hpp"
#include "rclcpp/callback_group.hpp"
#include "rclcpp/macros.hpp"
#include "rclcpp/node_interfaces/node_base_interface.hpp"
#include "rclcpp/node_interfaces/node_topics_interface.hpp"
#include "rclcpp/qos.hpp"
#include "rclcpp/serialized_message.hpp"
#include "rclcpp/subscription_base.hpp"
#include "rclcpp/typesupport_helpers.hpp"
#include "rclcpp/visibility_control.hpp"
- 解释:头文件保护机制确保该文件不会被多次包含。随后包含的头文件提供了实现
GenericSubscription
所需的基础设施,如rclcpp/subscription_base.hpp
定义了订阅者的基础类,rclcpp/serialized_message.hpp
用于处理序列化消息。
类定义
namespace rclcpp
{
/// %Subscription for serialized messages whose type is not known at compile time.
/**
* Since the type is not known at compile time, this is not a template, and the dynamic library
* containing type support information has to be identified and loaded based on the type name.
*
* It does not support intra-process handling.
*/
class GenericSubscription : public rclcpp::SubscriptionBase
{
public:
RCLCPP_SMART_PTR_DEFINITIONS(GenericSubscription)
-
解释:
GenericSubscription
类继承自SubscriptionBase
,用于表示通用的订阅者。由于消息类型在编译时未知,因此该类不是模板类,需要在运行时动态加载类型支持信息库。此外,该类不支持进程内通信。 -
智能指针定义:
RCLCPP_SMART_PTR_DEFINITIONS(GenericSubscription)
宏定义了GenericSubscription
类的智能指针类型(如SharedPtr
和ConstSharedPtr
),便于内存管理和资源共享。
构造函数
template<typename AllocatorT = std::allocator<void>>
GenericSubscription(
rclcpp::node_interfaces::NodeBaseInterface * node_base,
const std::shared_ptr<rcpputils::SharedLibrary> ts_lib,
const std::string & topic_name,
const std::string & topic_type,
const rclcpp::QoS & qos,
AnySubscriptionCallback<rclcpp::SerializedMessage, AllocatorT> callback,
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & options)
: SubscriptionBase(
node_base,
*rclcpp::get_message_typesupport_handle(topic_type, "rosidl_typesupport_cpp", *ts_lib),
topic_name,
options.to_rcl_subscription_options(qos),
options.event_callbacks,
options.use_default_callbacks,
DeliveredMessageKind::SERIALIZED_MESSAGE),
any_callback_(callback),
ts_lib_(ts_lib)
{
TRACETOOLS_TRACEPOINT(
rclcpp_subscription_init,
static_cast<const void *>(get_subscription_handle().get()),
static_cast<const void *>(this));
TRACETOOLS_TRACEPOINT(
rclcpp_subscription_callback_added,
static_cast<const void *>(this),
static_cast<const void *>(&any_callback_));
#ifndef TRACETOOLS_DISABLED
any_callback_.register_callback_for_tracing();
#endif
}
-
解释:
- 该构造函数用于初始化
GenericSubscription
对象。 node_base
是节点的基础接口指针,提供了与 ROS 2 节点相关的功能。ts_lib
是一个指向共享库的指针,动态加载消息类型的支持库。topic_name
和topic_type
分别表示订阅的话题名称和话题类型。qos
是质量服务 (QoS) 设置,用于配置消息的传输质量。callback
是处理新消息的回调函数。options
包含订阅者的选项,如事件回调和回调组。
- 该构造函数用于初始化
-
父类构造函数:
- 该构造函数首先调用
SubscriptionBase
的构造函数,传入节点基础接口、类型支持句柄、话题名称、订阅选项等参数。 - 然后,将类型支持库 (
ts_lib
) 存储在成员变量ts_lib_
中,以确保在订阅过程中该库始终处于加载状态。
- 该构造函数首先调用
-
跟踪工具集成:
TRACETOOLS_TRACEPOINT
用于集成跟踪工具,帮助在调试和性能分析时收集有关订阅者初始化和回调函数的信息。
析构函数
RCLCPP_PUBLIC
virtual ~GenericSubscription() = default;
- 解释:该析构函数声明为
virtual
以支持多态,使用默认析构函数即可。
创建消息和序列化消息的方法
// Same as create_serialized_message() as the subscription is to serialized_messages only
RCLCPP_PUBLIC
std::shared_ptr<void> create_message() override;
RCLCPP_PUBLIC
std::shared_ptr<rclcpp::SerializedMessage> create_serialized_message() override;
- 解释:
create_message()
方法用于创建一个通用的消息对象。由于GenericSubscription
订阅的是序列化消息,此方法与create_serialized_message()
的实现相同。create_serialized_message()
方法专门用于创建一个SerializedMessage
对象,表示收到的序列化消息。
处理消息和返回消息的方法
/// Cast the message to a rclcpp::SerializedMessage and call the callback.
RCLCPP_PUBLIC
void handle_message(
std::shared_ptr<void> & message, const rclcpp::MessageInfo & message_info) override;
/// Handle dispatching rclcpp::SerializedMessage to user callback.
RCLCPP_PUBLIC
void
handle_serialized_message(
const std::shared_ptr<rclcpp::SerializedMessage> & serialized_message,
const rclcpp::MessageInfo & message_info) override;
/// This function is currently not implemented.
RCLCPP_PUBLIC
void handle_loaned_message(
void * loaned_message, const rclcpp::MessageInfo & message_info) override;
- 解释:
handle_message()
方法将通用消息对象转换为SerializedMessage
对象,并调用用户定义的回调函数处理消息。handle_serialized_message()
方法专门用于处理SerializedMessage
对象,并将其分发给用户定义的回调函数。handle_loaned_message()
方法当前未实现。在某些 ROS 2 应用中,借用消息(loaned message)可以减少内存拷贝,提高效率。
动态类型支持的方法
// DYNAMIC TYPE ==================================================================================
RCLCPP_PUBLIC
rclcpp::dynamic_typesupport::DynamicMessageType::SharedPtr get_shared_dynamic_message_type()
override;
RCLCPP_PUBLIC
rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr get_shared_dynamic_message() override;
RCLCPP_PUBLIC
rclcpp::dynamic_typesupport::DynamicSerializationSupport::SharedPtr
get_shared_dynamic_serialization_support() override;
RCLCPP_PUBLIC
rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr create_dynamic_message() override;
RCLCPP_PUBLIC
void return_dynamic_message(
rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message) override;
RCLCPP_PUBLIC
void handle_dynamic_message(
const rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message,
const rclcpp::MessageInfo & message_info) override;
- 解释:
get_shared_dynamic_message_type()
方法返回一个共享指针,指向动态消息类型 (DynamicMessageType
) 的实例。该方法允许订阅者在运行时获取消息的类型信息。get_shared_dynamic_message()
方法返回一个共享指针,指向动态消息 (DynamicMessage
) 的实例。动态消息是一个通用的数据结构,可以表示任意类型的 ROS 消息。get_shared_dynamic_serialization_support()
方法返回一个共享指针,指向动态序列化支持 (DynamicSerializationSupport
) 的实例。这个对象提供了序列化和反序列化的功能。create_dynamic_message()
方法创建并返回一个新的动态消息实例,用于接收或处理消息。return_dynamic_message()
方法用于返回动态消息的对象,通常用于资源管理和内存释放。handle_dynamic_message()
方法用于处理动态消息,并将其传递给用户定义的回调函数。该方法提供了对动态类型的消息的支持,使得订阅者可以处理各种不同类型的消息。
私有成员与禁止复制
private:
RCLCPP_DISABLE_COPY(GenericSubscription)
AnySubscriptionCallback<rclcpp::SerializedMessage, std::allocator<void>> any_callback_;
// The type support library should stay loaded, so it is stored in the GenericSubscription
std::shared_ptr<rcpputils::SharedLibrary> ts_lib_;
};
- 解释:
RCLCPP_DISABLE_COPY(GenericSubscription)
:该宏禁用了GenericSubscription
类的复制构造函数和复制赋值运算符,防止对象被复制。这通常是因为类中包含了不能被复制的资源或需要唯一所有权的资源(如指针或文件句柄)。any_callback_
:这是一个AnySubscriptionCallback
对象,封装了处理序列化消息的回调函数。它允许用户在订阅者接收到消息时执行自定义的逻辑。ts_lib_
:该私有成员变量保存了指向类型支持库的共享指针 (std::shared_ptr<rcpputils::SharedLibrary>
),确保类型支持库在整个GenericSubscription
对象的生命周期内保持加载状态。这对于在运行时动态处理消息类型至关重要。
命名空间与头文件保护结束
} // namespace rclcpp
#endif // RCLCPP__GENERIC_SUBSCRIPTION_HPP_
- 解释:
- 这部分代码关闭了
rclcpp
命名空间,并结束了头文件的防重复包含保护 (#ifndef ... #endif
)。
- 这部分代码关闭了
总结
GenericSubscription
类为 ROS 2 提供了一种灵活的方式来订阅和处理序列化消息,特别是当消息类型在编译时未知时。该类通过动态加载类型支持库,支持运行时确定消息类型并处理。这种动态处理能力使得 GenericSubscription
特别适用于需要处理多种消息类型的复杂应用场景。
通过对 GenericSubscription
的详细解析,我们可以更好地理解其工作原理,包括如何初始化订阅者、如何动态加载消息类型支持库、如何处理和返回序列化消息以及动态消息等。