FAST DDS-GEN--通过 IDL 定义数据类型

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 类型
charchar
octetuint8_t
shortint16_t
unsigned shortuint16_t
longint32_t
unsigned longuint32_t
long longint64_t
unsigned long longuint64_t
floatfloat
doubledouble
long doublelong double
booleanbool
stringstd::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 允许使用以下名称作为基本类型的别名:int8uint8int16uint16int32uint32int64uint64

5.6 IDL 4.2 注释

IDL 注释有两种编写方式:

  1. 以 /* 开头的注释,以 */ 结尾,可跨多行。
  2. 以 // 开头的注释,注释内容持续到该行末尾,仅为单行注释。

如需了解更多 IDL 规范相关内容,请参考 IDL 4.2 规范文档(第 7.2 节 “词法约定”)。

/* MyStruct 结构体定义 */
struct MyStruct
{
    string mymessage;   // mymessage 数据成员
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值