5. 通过 IDL 定义数据类型
本节将介绍可通过 IDL 文件定义的数据类型,以及使用 IDL 文件构建数据类型的其他机制。
5.1 支持的 IDL 类型
需注意,Fast DDS-Gen 遵循 IDL 规范,默认不区分大小写。若需启用大小写敏感性,可在运行 Fast DDS-Gen 时使用 -cs
选项(详见 “支持的选项” 章节)。
警告:ROS 2 生成的 IDL 文件不一定能与 Fast DDS 应用程序兼容,因为这些文件的处理方式不同,可能会导致类型定义不兼容。若需了解如何确保 ROS 2 与 Fast DDS 应用程序之间的兼容性,请参考 Vulcanexus 相关教程。
5.1.1 基本类型
下表列出了 Fast DDS-Gen 支持的 IDL 基本类型,以及它们与 C++11 类型的映射关系:
IDL 类型 | C++11 类型 |
---|---|
char | char |
octet | uint8_t |
short | int16_t |
unsigned short | uint16_t |
long | int32_t |
unsigned long | uint32_t |
long long | int64_t |
unsigned long long | uint64_t |
float | float |
double | double |
long double | long double |
boolean | bool |
string | std::string |
5.1.2 数组
Fast DDS-Gen 支持一维数组和多维数组,数组均映射为 std::array
容器。下表列出了支持的数组类型及其映射关系:
IDL 类型 | C++11 类型 |
---|---|
char a[5] | std::array<char, 5> a |
octet a[5] | std::array<uint8_t, 5> a |
short a[5] | std::array<int16_t, 5> a |
unsigned short a[5] | std::array<uint16_t, 5> a |
long a[5] | std::array<int32_t, 5> a |
unsigned long a[5] | std::array<uint32_t, 5> a |
long long a[5] | std::array<int64_t, 5> a |
unsigned long long a[5] | std::array<uint64_t, 5> a |
float a[5] | std::array<float, 5> a |
double a[5] | std::array<double, 5> a |
5.1.3 序列
Fast DDS-Gen 支持序列(sequence),序列映射为 std::vector
容器。下表展示了 IDL 序列与 C++11 类型的映射关系:
IDL 类型 | C++11 类型 |
---|---|
sequence<char> | std::vector<char> |
sequence<octet> | std::vector<uint8_t> |
sequence<short> | std::vector<int16_t> |
sequence<unsigned short> | std::vector<uint16_t> |
sequence<long> | std::vector<int32_t> |
sequence<unsigned long> | std::vector<uint32_t> |
sequence<long long> | std::vector<int64_t> |
sequence<unsigned long long> | std::vector<uint64_t> |
sequence<float> | std::vector<float> |
sequence<double> | std::vector<double> |
5.1.4 映射
Fast DDS-Gen 支持映射(map),映射等同于 std::map
容器。其类型映射规则与序列一致。
IDL 类型 | C++11 类型 |
---|---|
map<char, unsigned long long> | std::map<char, uint64_t> |
注意:目前仅支持基本类型作为映射的键和值。
5.1.5 结构体
你可以定义一个包含多个不同类型成员的 IDL 结构体,它会被转换为一个 C++ 类 ——IDL 结构体中定义的成员会映射为该 C++ 类的私有数据成员,同时会生成 set()
和 get()
成员函数,用于访问这些私有数据成员。
以下是 IDL 结构体示例:
struct Structure
{
octet octet_value;
long long_value;
string string_value;
};
该结构体将被转换为以下 C++ 类:
class Structure
{
public:
Structure();
~Structure();
Structure(
const Structure& x);
Structure(
Structure&& x);
Structure& operator =(
const Structure& x);
Structure& operator =(
Structure&& x);
void octet_value(
uint8_t _octet_value);
uint8_t octet_value() const;
uint8_t& octet_value();
void long_value(
int64_t _long_value);
int64_t long_value() const;
int64_t& long_value();
void string_value(
const std::string
& _string_value);
void string_value(
std::string&& _string_value);
const std::string& string_value() const;
std::string& string_value();
private:
uint8_t m_octet_value;
int64_t m_long_value;
std::string m_string_value;
};
结构体可以继承其他结构体,从而扩展自身的成员集合:
struct ParentStruct
{
octet parent_member;
};
struct ChildStruct : ParentStruct
{
long child_member;
};
对应的 C++ 代码如下:
class ParentStruct
{
octet parent_member;
};
class ChildStruct : public ParentStruct
{
long child_member;
};
5.1.5.1 可选成员
结构体的成员可以设为可选成员,只需在该成员前添加 @optional
注解即可:
struct StructWithOptionalMember
{
@optional octet octet_opt;
};
可选成员会被转换为模板类 eprosima::fastcdr::optional<T>
(其中 T
为该成员的类型),对应的 C++ 类如下:
class StructWithOptionalMember
{
eprosima::fastcdr::optional<octet> octet_opt;
};
在读取可选成员的值之前,需先通过 has_value()
函数检查该成员是否有值。若访问未赋值的可选成员,会抛出 eprosima::fastcdr::exception::BadOptionalAccessException
异常:
if (octet_opt.has_value())
{
octet oc = octet_opt.value();
}
5.1.5.2 可扩展性
为支持类型在演进过程中保持互操作性,Fast DDS-Gen 支持 “类型可扩展性” 概念,包含三种可扩展类型:final(不可扩展)、appendable(可追加)和 mutable(可修改)。
- FINAL(不可扩展):表示类型定义固定,添加新成员会破坏类型的可赋值性。
- APPENDABLE(可追加):表示若两个类型中,一个包含另一个的所有成员且在末尾追加了新成员,这两个类型仍可保持可赋值性。
- MUTABLE(可修改):表示两个类型即使存在成员的添加、删除或顺序调整,仍可保持可赋值性。
@extensibility(FINAL)
struct FinalStruct
{
octet octet_opt;
};
@extensibility(APPENDABLE)
struct AppendableStruct
{
octet octet_opt;
};
@extensibility(MUTABLE)
struct MutableStruct
{
octet octet_opt;
};
注意:当使用 XCDRv1 编码算法时,若一个可追加(appendable)结构体作为另一个结构体的成员,可能无法正确反序列化。
5.1.6 联合体
在 IDL 中,联合体(union)定义为一组包含各自类型的成员,以及一个用于指定当前使用成员的判别器(discriminant)。IDL 联合体类型会映射为一个 C++ 类,该类包含访问联合体成员和判别器的成员函数。
以下是 IDL 联合体示例:
union Union switch(long)
{
case 1:
octet octet_value;
case 2:
long long_value;
case 3:
string string_value;
};
该联合体将被转换为以下 C++ 类:
class Union
{
public:
Union();
~Union();
Union(
const Union& x);
Union(
Union&& x);
Union& operator =(
const Union& x);
Union& operator =(
Union&& x);
void d(
int32_t __d);
int32_t _d() const;
int32_t& _d();
void octet_value(
uint8_t _octet_value);
uint8_t octet_value() const;
uint8_t& octet_value();
void long_value(
int64_t _long_value);
int64_t long_value() const;
int64_t& long_value();
void string_value(
const std::string
& _string_value);
void string_value(
std::string&& _string_value);
const std::string& string_value() const;
std::string& string_value();
private:
int32_t m__d;
uint8_t m_octet_value;
int64_t m_long_value;
std::string m_string_value;
};
5.1.7 位集
位集(bitset)是一种特殊的结构体,用于封装一组位,最多可表示 64 位。每个成员都定义为位域(bitfield),便于访问位集的某一部分。
bitset MyBitset
{
bitfield<3> a;
bitfield<10> b;
bitfield<12, long> c;
};
MyBitset
类型共存储 25 位(3 + 10 + 12),在内存中需占用 32 位(能存储该位集大小的最小基本类型)。
- 位域
a
可访问前 3 位(位 0 至位 2)。 - 位域
b
可访问接下来的 10 位(位 3 至位 12)。 - 位域
c
可访问再接下来的 12 位(位 13 至位 24)。
对应的 C++ 代码如下:
class MyBitset
{
public:
void a(
char _a);
char a() const;
void b(
uint16_t _b);
uint16_t b() const;
void c(
int32_t _c);
int32_t c() const;
private:
std::bitset<25> m_bitset;
};
在内部,位集以 std::bitset
形式存储。对于每个位域,会生成 get()
和 set()
成员函数,且使用能访问该位域的最小无符号基本类型。以位域 c
为例,由于用户指定其访问类型为 long
,因此生成的代码使用 int32_t
,而非自动选择 uint16_t
。
位集可以继承其他位集,从而扩展自身的成员集合:
bitset ParentBitset
{
bitfield<3> parent_member;
};
bitset ChildBitset : ParentBitset
{
bitfield<10> child_member;
};
对应的 C++ 代码如下:
class ParentBitset
{
std::bitset<3> parent_member;
};
class ChildBitset : public ParentBitset
{
std::bitset<10> child_member;
};
需注意,此时 ChildBitset
会包含两个 std::bitset
数据成员,一个来自 ParentBitset
,另一个来自 ChildBitset
自身。
5.1.8 枚举
IDL 格式的枚举(enumeration)是一组带有关联数值的标识符集合。IDL 枚举类型会直接映射为对应的 C++11 枚举定义。
以下是 IDL 枚举示例:
enum Enumeration
{
RED,
GREEN,
@value(3)
BLUE
};
该枚举将被转换为以下 C++ 代码:
enum Enumeration : int32_t
{
RED,
GREEN,
BLUE = 3
};
5.1.9 位掩码
位掩码(bitmask)是一种特殊的枚举,用于管理位掩码,支持根据位的位置定义位掩码。
以下是 IDL 位掩码示例:
@bit_bound(8)
bitmask MyBitMask
{
@position(0) flag0,
@position(1) flag1,
@position(4) flag4,
@position(6) flag6,
flag7
};
该位掩码将被转换为以下 C++ 代码:
enum MyBitMask : uint8_t
{
flag0 = 0x01 << 0,
flag1 = 0x01 << 1,
flag4 = 0x01 << 4,
flag6 = 0x01 << 6,
flag7 = 0x01 << 7
};
@bit_bound
注解用于定义关联枚举的宽度,其值必须是 1 至 64 之间的正整数;若省略该注解,默认宽度为 32 位。对于每个 flag
,用户可使用 @position
注解定义其位位置;若省略该注解,位位置会从最后一个已定义 flag 的位置开始自动递增(初始从 0 开始)。
5.1.10 模块
为避免变量名冲突,可在 IDL 文件中定义模块(module)。模块会被转换为 C++ 中的命名空间(namespace)。
5.1.11 带键的数据类型
若要使用键控主题(keyed topic),需在结构体中定义一些键成员,只需在作为键的结构体成员前添加 @key
注解即可。例如,在以下 IDL 文件中,id
和 type
字段将作为键:
struct MyType
{
@key long id;
@key string type;
long positionX;
long positionY;
};
Fast DDS-Gen 会自动检测这些注解,并在 TopicDataType
中正确生成用于键生成函数(getKey()
)的序列化方法。该函数会对键成员的大端序序列化结果计算 128 位 MD5 摘要。
5.2 包含其他 IDL 文件
除当前 IDL 文件外,还可包含其他 IDL 文件。Fast DDS-Gen 会使用 C/C++ 预处理器处理文件包含,可通过 #include
指令包含 IDL 文件。同时,建议使用预处理器指令防止同一 IDL 文件被多次包含。
#include "OtherFile.idl"
#include <AnotherFile.idl>
#include <IncludedIDL.idl>
若 Fast DDS-Gen 在系统默认路径中未找到 C/C++ 预处理器,可使用 -ppPath
参数指定预处理器路径;若需禁用 C/C++ 预处理器,可使用 -ppDisable
参数。
此外,还可使用 -extrastg
参数为包含的 IDL 文件应用自定义模板。要启用该功能,传递给 -extrastg
的输出文件名必须包含字符 @
,该字符会被替换为包含文件的名称,以生成最终的输出文件。
以下命令将为主 IDL 文件和包含的 IDL 文件分别生成两个自定义模板(假设 IncludedIDL.idl
被包含在 MainIDL.idl
中),生成的文件为 MainIDL_Custom.cpp
和 IncludedIDL_Custom.cpp
:
<path/to/Fast DDS-Gen>/scripts/fastddsgen MainIDL.idl -I <path/to/idls> -extrastg <path/to/template>/Custom.stg @_Custom.cpp
5.3 注解
Fast DDS-Gen 允许用户根据 OMG IDL 规范定义和使用自定义注解:
@annotation MyAnnotation
{
long value;
string name;
};
此外,规范还定义了以下标准注解,这些注解为内置注解(无需定义即可使用):
内置注解 | 作用 | 支持情况 |
---|---|---|
@ami | 异步接口或操作 | ❌ |
@appendable | @extensibility(APPENDABLE) 的简写 | ✅ |
@autoid | 若未使用 @id 注解显式设置成员 ID,用于配置成员 ID 生成算法。可选值为 SEQUENTIAL (成员 ID 按顺序分配,默认值)或 HASH (成员 ID 通过哈希成员名的算法计算)。该注解可用于模块、结构体或联合体声明 | ✅ |
@bit_bound | 设置位掩码和枚举底层基本类型的位数。目前,@bit_bound 仅可应用于位掩码类型 | ✅❌ |
@data_representation | 为特定类型指定所需的 DataRepresentationId | ❌ |
@default | 为成员设置常量默认值 | ❌ |
@default_literal | 将枚举字面量标记为默认值 | ❌ |
@default_nested | 在模块声明中使用,标记模块内定义的所有元素均为 @nested 类型 | ❌ |
@extensibility | 可应用于任意构造元素,指定元素的演进规则。详见 “可扩展性” 章节 | ✅ |
@external | 成员存储在外部存储中 | ✅ |
@final | @extensibility(FINAL) 的简写 | ✅ |
@hashid | 使用指定字符串计算成员 ID;若字符串为空,则使用成员名计算 | ✅ |
@id | 为结构体或联合体成员分配成员 ID | ✅ |
@ignore_literal_names | 检查演进类型兼容性时,是否考虑成员名 | ❌ |
@key | 将结构体成员标记为键的一部分。也支持 @Key 。详见 “主题、键和实例” 章节 | ✅ |
@max | 为成员设置常量最大值 | ❌ |
@min | 为成员设置常量最小值 | ❌ |
@must_understand | 将结构体成员标记为结构体完整性的必要成员 | ❌ |
@mutable | @extensibility(MUTABLE) 的简写 | ✅ |
@nested | 类型始终在另一个类型内部使用 | ❌ |
@non_serialized | 序列化时忽略该成员 | ❌ |
@oneway | 单向操作,信息仅单向流动 | ❌ |
@optional | 将结构体成员配置为可选成员。详见 “可选成员” 章节 | ✅ |
@position | 为位掩码中的位标志设置位置 | ✅ |
@range | 为成员设置允许值的范围 | ❌ |
@service | 将接口视为服务 | ❌ |
@feed | 在接口操作中使用,指示数据流式传输(详见 “定义 IDL 接口” 章节) | ✅ |
@topic | 结构体或联合体拟用作主题数据类型 | ❌ |
@try_construct | 检查演进类型兼容性时,配置集合 / 聚合类型的构造行为及失败处理方式 | ❌ |
@unit | 为成员指定计量单位 | ❌ |
@value | 为枚举字面量设置常量值 | ✅ |
@verbatim | 为元素添加注释或文本 | ❌ |
5.4 前向声明
Fast DDS-Gen 支持前向声明,可用于声明相互依赖的结构体、联合体等类型:
struct ForwardStruct;
union ForwardUnion;
struct ForwardStruct
{
ForwardUnion fw_union;
};
union ForwardUnion switch (long)
{
case 0:
ForwardStruct fw_struct;
default:
string empty;
};
5.5 IDL 4.2 别名
IDL 4.2 允许使用以下名称作为基本类型的别名:int8
、uint8
、int16
、uint16
、int32
、uint32
、int64
、uint64
5.6 IDL 4.2 注释
IDL 注释有两种编写方式:
- 以
/*
开头的注释,以*/
结尾,可跨多行。 - 以
//
开头的注释,注释内容持续到该行末尾,仅为单行注释。
如需了解更多 IDL 规范相关内容,请参考 IDL 4.2 规范文档(第 7.2 节 “词法约定”)。
/* MyStruct 结构体定义 */
struct MyStruct
{
string mymessage; // mymessage 数据成员
};