Fast-CDR 源码详细分析

Fast-CDR 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/eProsima/Fast-CDR
版本:1.0.24configure.ac),构建类型 CMake,语言 C++11,许可证 Apache 2.0

eProsima Fast-CDR 是一个轻量级 C++ 序列化库,提供两套 CDR(Common Data Representation)机制:标准 CDRCdr)与 Fast CDRFastCdr)。在 ROS 2 Humble 工作区中,它是 Fast-DDS 的底层依赖,也是 rosidl_typesupport_fastrtps 生成代码所依赖的序列化引擎——所有通过 Fast DDS 传输的 ROS 消息,最终都经由 Fast-CDR 写入/读出字节流。


1. 总体认识

1.1 核心职责

能力 说明
标准 CDR 序列化 遵循 CORBA/DDS 规范,含字节对齐、大小端转换、Encapsulation 头
Fast CDR 序列化 eProsima 自研的简化协议,不做对齐,连续写入,性能更高
缓冲区管理 FastBuffer 提供内部/外部两种内存模式,支持自动扩容
类型覆盖 基本类型、字符串、数组、序列、自定义类型(通过 serialize()/deserialize() 回调)
异常安全 序列化失败时可通过 state 回滚到出错前位置

1.2 在 ROS 2 栈中的位置

ROS 2 应用层类型支持层DDS 中间件网络/共享内存rclcpp Nodestd_msgs / 自定义 msgrosidl_typesupport_fastrtps_cppfastrtps 生成的 serialize/deserializeFast-DDSFast-CDRRTPS 报文 payload
消费者 使用方式
Fast-DDS 直接链接 libfastcdr,序列化/反序列化 RTPS payload
rosidl_typesupport_fastrtps 生成代码调用 eprosima::fastcdr::Cdr<</>>Cdr::alignment()
Fast-DDS-Gen / rosidl 工具链 为 IDL/msg 生成 serialize(Cdr&) / deserialize(Cdr&) 成员函数

注意:ROS 2 默认 RMW 实现(Fast DDS)走 Cdr(标准 CDR) 路径;FastCdr 主要用于 eProsima 内部或对性能更敏感、且双方约定使用 Fast CDR 协议的场景。


2. 目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Fast-CDR/
├── include/fastcdr/ # 公开头文件
│ ├── Cdr.h # 标准 CDR(3557 行,核心 API)
│ ├── FastCdr.h # Fast CDR(2104 行)
│ ├── FastBuffer.h # 字节缓冲区 + iterator
│ ├── config.h.in # 构建时生成的配置宏
│ ├── fastcdr_dll.h # DLL 导出 / 自动链接
│ ├── eProsima_auto_link.h # MSVC 自动链接
│ └── exceptions/ # 异常类
│ ├── Exception.h
│ ├── NotEnoughMemoryException.h
│ └── BadParamException.h
├── src/cpp/ # 实现(6 个 .cpp,约 3600 行)
│ ├── Cdr.cpp # 2746 行
│ ├── FastCdr.cpp # 813 行
│ ├── FastBuffer.cpp # 104 行
│ └── exceptions/
├── test/ # gtest 单元测试
│ ├── SimpleTest.cpp # 6931 行,覆盖全部基本类型
│ └── ResizeTest.cpp
├── cmake/ # 构建辅助、打包
├── configure.ac # 版本号 autotools 源
├── CMakeLists.txt # 主构建入口
└── colcon.pkg # colcon 元数据

源码体量很小:约 65 个文件,核心逻辑集中在 3 个头文件 + 3 个实现文件中。


3. FastBuffer — 字节流容器

路径:include/fastcdr/FastBuffer.hsrc/cpp/FastBuffer.cpp

3.1 两种构造模式

构造方式 行为
FastBuffer() 内部分配内存(m_internalBuffer = true),析构时 free()
FastBuffer(char* buf, size_t size) 借用外部缓冲区,不释放

3.2 内存扩容策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bool FastBuffer::resize(
size_t minSizeInc)
{
size_t incBufferSize = BUFFER_START_LENGTH;

if (m_internalBuffer)
{
if (minSizeInc > BUFFER_START_LENGTH)
{
incBufferSize = minSizeInc;
}

if (m_buffer == NULL)
{
m_bufferSize = incBufferSize;

m_buffer = reinterpret_cast<char*>(malloc(m_bufferSize));
// ...
}
else
{
m_bufferSize += incBufferSize;

m_buffer = reinterpret_cast<char*>(realloc(m_buffer, m_bufferSize));
// ...
}
}

return false;
}

要点:

  • 初始分配 200 字节BUFFER_START_LENGTH
  • 每次扩容至少增加 200 字节,或按 minSizeInc 增量
  • 外部缓冲区模式resize() 返回 false,序列化超出容量会抛 NotEnoughMemoryException

3.3 _FastBuffer_iterator — 高效读写迭代器

迭代器封装了 memcpy 读写,避免逐字节循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename _T>
inline
void operator <<(
const _T& data)
{
memcpy(m_currentPosition, &data, sizeof(_T));
}

template<typename _T>
inline
void operator >>(
_T& data)
{
memcpy(&data, m_currentPosition, sizeof(_T));
}

特殊操作符:

  • << iterator:切换底层 buffer 指针,保持相对偏移
  • >> iterator:从另一个 iterator 同步位置索引
  • memcopy / rmemcopy:批量拷贝

4. Cdr — 标准 CDR 序列化

路径:include/fastcdr/Cdr.hsrc/cpp/Cdr.cpp

4.1 关键枚举与配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef enum
{
//! @brief Common CORBA CDR serialization.
CORBA_CDR,
//! @brief DDS CDR serialization.
DDS_CDR
} CdrType;

typedef enum : uint8_t
{
DDS_CDR_WITHOUT_PL = 0x0,
DDS_CDR_WITH_PL = 0x2
} DDSCdrPlFlag;

typedef enum : uint8_t
{
BIG_ENDIANNESS = 0x0,
LITTLE_ENDIANNESS = 0x1
} Endianness;

static const Endianness DEFAULT_ENDIAN;
  • CORBA_CDR:经典 CORBA CDR,无 Encapsulation 头
  • DDS_CDR:DDS 扩展,含 dummy byte、encapsulation kind、options
  • 默认字节序由编译目标决定(FASTCDR_IS_BIG_ENDIAN_TARGET

4.2 Encapsulation(封装头)

DDS 消息在 payload 开头写入/读取 encapsulation,用于声明字节序和 Parameter List 标志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Cdr& Cdr::read_encapsulation()
{
// DDS_CDR: 先读 dummy byte(必须为 0)
// 再读 encapsulationKind(低 1 位 = 字节序,bit1 = PL 标志)
// 若字节序与当前不同 → 切换 m_swapBytes
// DDS_CDR: 再读 m_options (uint16)
resetAlignment();
return *this;
}

Cdr& Cdr::serialize_encapsulation()
{
// DDS_CDR: 写 dummy=0
// 写 encapsulationKind = m_plFlag | m_endianness
// DDS_CDR: 写 m_options
resetAlignment();
return *this;
}

Encapsulation 字节布局(DDS_CDR):

1
2
3
[ dummy:1B=0 ] [ encapsulationKind:1B ] [ options:2B ]
├ bit0: endianness
└ bit1: parameter list flag

4.3 字节对齐机制

标准 CDR 的核心特征:多字节类型必须按自身大小对齐

静态对齐计算(供 rosidl 生成代码预估序列化大小):

1
2
3
4
5
6
inline static size_t alignment(
size_t current_alignment,
size_t dataSize)
{
return (dataSize - (current_alignment % dataSize)) & (dataSize - 1);
}

实例对齐(序列化过程中):

1
2
3
4
5
6
7
8
9
10
11
12
13
inline size_t alignment(
size_t dataSize) const
{
return dataSize >
m_lastDataSize ? (dataSize - ((m_currentPosition - m_alignPosition) % dataSize)) &
(dataSize - 1) : 0;
}

inline void makeAlign(
size_t align)
{
m_currentPosition += align;
}

优化:若当前要序列化的类型大小 ≤ 上一个类型大小(m_lastDataSize),则无需额外对齐字节——这是 CDR 规范中的 packed 优化。

int16_t 序列化示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Cdr& Cdr::serialize(
const int16_t short_t)
{
size_t align = alignment(sizeof(short_t));
size_t sizeAligned = sizeof(short_t) + align;

if (((m_lastPosition - m_currentPosition) >= sizeAligned) || resize(sizeAligned))
{
m_lastDataSize = sizeof(short_t);
makeAlign(align);

if (m_swapBytes)
{
const char* dst = reinterpret_cast<const char*>(&short_t);
m_currentPosition++ << dst[1];
m_currentPosition++ << dst[0];
}
else
{
m_currentPosition << short_t;
m_currentPosition += sizeof(short_t);
}
return *this;
}
throw NotEnoughMemoryException(...);
}

4.4 大小端转换

  • 构造时:m_swapBytes = (endianness != DEFAULT_ENDIAN)
  • 读 encapsulation 时可能动态切换
  • 多字节类型在 m_swapBytes == true 时逐字节反转写入/读出
  • 提供带 Endianness 参数的 serialize(T, Endianness) 重载,临时切换字节序

4.5 字符串编码

类型 序列化格式
const char* / std::string uint32 length(含 \0)+ 字符数据
const wchar_t* / std::wstring uint32 char_count + 每字符 4 字节(Windows 逐字符,Linux 批量 memcopy)
bool 1 字节:01
long double 对齐到 8 字节边界,占 16 字节(8 字节平台前 8 字节填 0)

4.6 序列 / 数组

  • std::vector<T>:先写 int32 长度,再写元素数组
  • 序列化失败时通过 state 回滚(保证原子性)
  • std::vector<bool> 有特殊模板特化(MSVC / 非 MSVC 分支不同)

4.7 自定义类型

非基本类型通过模板调用对象的成员函数:

1
2
3
4
5
6
template<class _T>
inline Cdr& operator <<(const _T& type_t)
{
type_t.serialize(*this);
return *this;
}

rosidl / Fast-DDS-Gen 为每个 struct 生成 void serialize(eprosima::fastcdr::Cdr&) constvoid deserialize(eprosima::fastcdr::Cdr&)

4.8 state — 序列化快照

Cdr::state 保存四个字段,用于出错回滚或嵌套序列化:

字段 含义
m_currentPosition 当前读写位置
m_alignPosition 对齐基准位置
m_swapBytes 是否字节交换
m_lastDataSize 上次序列化类型大小

5. FastCdr — 无对齐快速序列化

路径:include/fastcdr/FastCdr.hsrc/cpp/FastCdr.cpp

5.1 与 Cdr 的核心差异

特性 Cdr FastCdr
字节对齐 ✅ 按 CDR 规范 ❌ 无对齐,紧凑排列
Encapsulation ✅ CORBA/DDS ❌ 无
大小端 ✅ 支持切换 ❌ 不做字节交换
数组批量写入 对齐后逐元素或 memcopy 直接 memcopy 整块
state 字段 4 个 1 个(仅 position)
典型用途 DDS/ROS 2 互操作 eProsima 内部高性能场景

5.2 基本类型序列化

int16_t 为例——无对齐,直接 memcpy

1
2
3
4
5
6
7
8
9
10
11
FastCdr& serialize(
const int16_t short_t)
{
if (((m_lastPosition - m_currentPosition) >= sizeof(short_t)) || resize(sizeof(short_t)))
{
m_currentPosition << short_t;
m_currentPosition += sizeof(short_t);
return *this;
}
throw exception::NotEnoughMemoryException(...);
}

数组类型直接批量拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FastCdr& FastCdr::serializeArray(
const int16_t* short_t,
size_t numElements)
{
size_t totalSize = sizeof(*short_t) * numElements;

if (((m_lastPosition - m_currentPosition) >= totalSize) || resize(totalSize))
{
m_currentPosition.memcopy(short_t, totalSize);
m_currentPosition += totalSize;
return *this;
}
throw NotEnoughMemoryException(...);
}

5.3 long double 平台差异

FastCdr 对 long double 做了大量平台分支:

  • 16 字节平台:直接 memcopy
  • 8 字节平台:写 16 字节(前 8 字节填 0,后 8 字节为值)
  • 支持 __float128:转换为 128 位浮点再写入

这与 Cdr 的处理逻辑一致,保证与 DDS XTypes 128-bit float 布局兼容。


6. 异常体系

路径:include/fastcdr/exceptions/src/cpp/exceptions/

classDiagram
    class exception {
        <<std::exception>>
    }
    class Exception {
        +raise()*
        +what() const
        -m_message
    }
    class NotEnoughMemoryException {
        缓冲区空间不足
    }
    class BadParamException {
        非法参数/格式
    }
    exception <|-- Exception
    Exception <|-- NotEnoughMemoryException
    Exception <|-- BadParamException
异常 触发场景
NotEnoughMemoryException 缓冲区剩余空间不足且无法 resize
BadParamException encapsulation 格式错误、bool 非法值、空指针等

设计特点:

  • 继承 std::exception,同时提供 raise() 用于 re-throw(配合 state 回滚)
  • 序列化复合类型(vector、sequence)在 catch 块中 setState(state_before_error)ex.raise()

7. 构建与配置

7.1 CMake 要点

  • 产物:libfastcdr.so(默认 shared)
  • 版本:从 configure.ac 读取 → 1.0.24
  • 编译特性检测:check_endianness()check_type_sizes()check_stdcxx()
  • 生成 config.h:C++11 支持、大小端、FASTCDR_SIZEOF_LONG_DOUBLE

7.2 config.h 关键宏

1
2
3
4
5
6
#define FASTCDR_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define FASTCDR_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define FASTCDR_VERSION_MICRO @PROJECT_VERSION_PATCH@
#define FASTCDR_IS_BIG_ENDIAN_TARGET @FASTCDR_IS_BIG_ENDIAN_TARGET@
#define FASTCDR_HAVE_FLOAT128 @FASTCDR_HAVE_FLOAT128@
#define FASTCDR_SIZEOF_LONG_DOUBLE @FASTCDR_SIZEOF_LONG_DOUBLE@

7.3 colcon 集成

1
2
3
4
5
{
"name": "fastcdr",
"type": "cmake",
"dependencies": ["googletest-distribution"]
}

在 ROS 2 工作区中,fastcdr 作为 Fast-DDS 的前置依赖 被 colcon 自动构建。

7.4 DLL 导出

fastcdr_dll.h 定义 Cdr_DllAPI 宏:

  • Windows 动态库:__declspec(dllexport/dllimport)
  • Linux:空宏(符号默认可见)
  • 支持 EPROSIMA_ALL_DYN_LINK / FASTCDR_DYN_LINK 全局开关

8. 测试

8.1 SimpleTest.cpp

6931 行,使用 gtest,覆盖:

  • 全部基本类型的 serialize/deserialize 往返
  • CORBA_CDR 与 DDS_CDR 两种模式
  • 大端 / 小端
  • 数组、vector、嵌套 array
  • encapsulation 读写
  • state 回滚
  • 空字符串、边界值

8.2 ResizeTest.cpp

专门测试 FastBuffer 内部缓冲区的自动扩容行为。


9. ROS 2 集成详解

9.1 rosidl_typesupport_fastrtps 生成代码

rosidl_typesupport_fastrtps_cpp 为每个 msg 生成:

  1. get_serialized_size() — 调用 Cdr::alignment() 静态方法预估大小
  2. serialize() — 创建 FastBuffer + Cdr,写 encapsulation,逐字段 <<
  3. deserialize() — 读 encapsulation,逐字段 >>

生成模板片段(msg__type_support.cpp.em):

1
2
3
4
eprosima::fastcdr::Cdr::alignment(current_alignment, sizeof(uint32_t));
// ... 每个字段累加对齐与大小
void serialize(eprosima::fastcdr::Cdr & cdr) { /* scdr << field */ }
void deserialize(eprosima::fastcdr::Cdr & cdr) { /* dcdr >> field */ }

9.2 典型序列化流程(Fast DDS RMW)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ROS msg 对象


rosidl_typesupport_fastrtps_cpp::serialize()

├─ FastBuffer(buffer, size) 或 FastBuffer() + reserve
├─ Cdr scdr(buffer, DEFAULT_ENDIAN, DDS_CDR)
├─ scdr.serialize_encapsulation()
├─ msg.serialize(scdr) // 生成的代码


Fast-DDS 将 buffer 作为 Sample payload 发送


对端 Cdr dcdr(buffer, ..., DDS_CDR)
├─ dcdr.read_encapsulation()
└─ msg.deserialize(dcdr)

9.3 与 CycloneDDS 的对比

中间件 序列化库 CDR 实现
Fast DDS Fast-CDR eprosima::fastcdr::Cdr
CycloneDDS 内置 CDR ddsc 内部实现,不依赖 Fast-CDR

两者 payload 格式均遵循 OMG CDR 规范,但实现独立;同一 msg 在两种 RMW 间二进制 payload 通常兼容(相同 encapsulation + 对齐规则)。


10. Cdr 与 FastCdr 选型

需要序列化?需要与标准 DDS/CORBA 互操作?Cdr + DDS_CDR/CORBA_CDR双方都是 eProsima 且约定 Fast CDR?FastCdrDDS 消息?serialize_encapsulation / read_encapsulation直接写字段

11. 设计特点小结

特点 说明
双引擎 标准兼容(Cdr)+ 性能优化(FastCdr)
Header-heavy 大量 inline 模板在头文件,.cpp 只实现复杂逻辑
零拷贝倾向 iterator + memcopy 批量操作;外部 buffer 模式避免内存复制
异常 + state 回滚 复合类型序列化失败时不留半成品
跨平台 long double 8/16 字节平台分支 + __float128 支持
ROS 2 QL1 声明 Quality Level 1(见 QUALITY.md
轻量 核心库仅 ~3600 行实现代码

12. 推荐阅读顺序

  1. 缓冲区基础FastBuffer.hFastBuffer.cpp — 理解内存模式与 iterator
  2. 标准 CDR 核心Cdr.cppserialize(int16_t)read_encapsulation() — 对齐与 encapsulation
  3. Fast CDR 对比FastCdr.h 中同名 serialize(int16_t) — 体会无对齐差异
  4. ROS 2 生成代码rosidl_typesupport_fastrtps_cpp/resource/msg__type_support.cpp.em
  5. Fast-DDS 使用点Fast-DDS/src/cpp/fastdds/dds/ 中搜索 fastcdr::Cdr
  6. 测试验证test/SimpleTest.cpp 前 200 行 — 了解 API 用法模式
  7. 配置宏:构建后查看 build/fastcdr/include/fastcdr/config.h

13. API 速查

13.1 基本用法(Cdr)

1
2
3
4
5
6
7
8
9
10
11
12
#include <fastcdr/Cdr.h>
#include <fastcdr/FastBuffer.h>

char buffer[512];
eprosima::fastcdr::FastBuffer fastbuffer(buffer, sizeof(buffer));
eprosima::fastcdr::Cdr ser(fastbuffer, eprosima::fastcdr::Cdr::DEFAULT_ENDIAN,
eprosima::fastcdr::Cdr::DDS_CDR);

ser.serialize_encapsulation();
ser << my_msg; // 或 my_msg.serialize(ser)

size_t len = ser.getSerializedDataLength();

13.2 基本用法(FastCdr)

1
2
3
4
5
eprosima::fastcdr::FastBuffer fastbuffer;
eprosima::fastcdr::FastCdr ser(fastbuffer);

ser << int32_t{42} << std::string{"hello"};
// 无 encapsulation,无对齐

13.3 关键公开方法

方法 用途
FastBuffer getBuffer(), reserve(), resize() 缓冲区访问与扩容
Cdr serialize_encapsulation(), read_encapsulation() DDS 封装头
Cdr alignment(), resetAlignment() 对齐计算与控制
Cdr getState(), setState() 快照/回滚
Cdr changeEndianness() 动态切换字节序
Cdr/FastCdr getSerializedDataLength() 已序列化字节数
Cdr/FastCdr operator<< / operator>> 流式序列化/反序列化

文档基于 ROS 2 Humble 工作区中的 Fast-CDR 1.0.24 源码分析生成。

文章互动

阅读 --

留言

0 条留言

正在加载留言…