概述
当我们的系统变得复杂,且需要多种不同类型的数据结构在不同的节点通信时,最佳实践是将IDL文件按功能模块进行拆分和组织,而不是仅仅将所有定义都放在一个巨大的文件中。
这类似于在C++中将类的声明分散到不同的头文件中,而不是把所有的东西都写在里main.cpp
核心思想:一个功能模块,一个IDL文件
示例
idl
// robot_types.idl
module robot {
struct Header {
uint64 sec;
uint32 nsec;
uint32 seq;
};
struct ImuRaw {
Header header;
float ax; float ay; float az;
float gx; float gy; float gz;
};
struct MotorPwm {
Header header;
float left;
float right;
};
};
1. IDL 语法详解 (以 robot_types.idl
为例)
IDL 全称是接口定义语言 (Interface Definition Language)。它是一种与特定编程语言无关的规范语言,用于定义数据类型和接口。DDS 使用它来确保不同的应用程序(即使是用不同语言编写的)能够理解和交换数据。
idl
module robot {
// 具体定义结构体
};
module
: 这相当于 C++ 中的namespace
或 Java 中的package
。它用于组织系统的数据类型,避免命名冲突。上面的示例中,定义的所有结构体都将属于robot
这个模块。在生成的代码中,这通常会体现为名称前缀,例如robot_Header
。
idl
struct Header {
uint64 sec;
uint32 nsec;
uint32 seq;
};
struct
: 定义一个数据结构,类似于 C/C++ 中的struct
。它将多个数据成员(字段)组合成一个单一的类型。uint64
,uint32
,float
: 这些是 IDL 中的基本数据类型。它们与 C++ 中的类型有直接的对应关系:short
,long
,long long
: 对应 C++ 的int16_t
,int32_t
,int64_t
unsigned short
,unsigned long
,unsigned long long
: 对应 C++ 的uint16_t
,uint32_t
,uint64_t
(注意:在 IDL 中long
是32位,long long
是64位)float
,double
: 对应 C++ 的float
,double
char
,wchar
: 字符类型boolean
: 布尔类型octet
: 8位字节,对应 C++ 的uint8_t
idl
struct ImuRaw {
Header header; //
float ax; float ay; float az;
float gx; float gy; float gz;
}; //
struct MotorPwm {
Header header;
float left;
float right;
};
- 嵌套结构体 : 可以在一个
struct
中包含另一个已经定义的struct
。这里ImuRaw
和MotorPwm
都包含了Header
结构体,实现了代码复用。
其它常用 IDL 语法元素:
-
string
: 定义一个字符串。string<10>
表示最大长度为10的字符串。 -
sequence
: 定义一个动态数组(序列)。sequence<float>
表示一个float
类型的数组。sequence<float, 5>
表示一个最多包含5个float
元素的数组。 -
enum
: 定义枚举类型。idlenum Color { RED, GREEN, BLUE };
-
const
: 定义常量。idlconst long MAX_SPEED = 100;
2. 生成类型支持代码
为了让 CycloneDDS 能够理解在 IDL 中定义的结构体,我们需要使用 IDL 编译器将 .idl
文件转换成特定语言(C/C++)的代码。CycloneDDS 提供的这个工具叫做 idlc
。
idlc
工具会读取 .idl
文件,并生成两部分内容:
- C/C++ 头文件 (
.h
): 包含在 IDL 中定义的结构体的 C/C++ 版本。 - C/C++ 源文件 (
.c
): 包含 DDS 类型描述符(Type Descriptor)。这个描述符包含了关于数据结构的元信息(如字段名、类型、偏移量等),DDS 中间件在序列化和反序列化数据时需要用到它。
如何使用 idlc
idlc
是一个命令行工具,在成功编译和安装 CycloneDDS 后,应该就可以用了
最基本的使用方法是:(不推荐)
bash
# -l c 表示生成 C 语言代码 (兼容 C++)
idlc -l c your_idl_file.idl
对于上面的示例程序,命令应该是:
bash
idlc -l c idl/robot_types.idl
执行后,会在当前目录下生成 robot_types.h
和 robot_types.c
两个文件。
3. 集成到 CMake 项目中(推荐)
手动运行 idlc
很繁琐且容易出错。最佳实践是将其集成到 CMakeLists.txt
构建流程中,让它自动完成。
我们可以创建一个自定义命令 (add_custom_command
) 和一个自定义目标 (add_custom_target
) 来实现这一点。
CMakeLists.txt
的具体实现可以看我的另一篇文章
总结步骤:
- 定义数据 : 使用 IDL 语法在
.idl
文件中清晰地定义跨平台、跨语言的数据结构。 - 编译 IDL(可选) : 使用
idlc -l c <your_file.idl>
命令生成 C 语言兼容的头文件 (.h
) 和源文件 (.c
)。 - 集成构建 : 将
idlc
命令集成到 CMake 或其他构建系统中,实现自动化代码生成。 - 使用代码 : 在自己的程序中
#include
生成的.h
文件,并在调用dds_create_topic
等函数时,使用idlc
生成的类型描述符(如&robot_ImuRaw_desc
)。
关于警告
No default extensibility provided. For one or more of the aggregated types in the IDL the extensibility is not explicitly set. Currently the default extensibility for these types is 'final', but this may change to 'appendable' in a future release because that is the default in the DDS XTypes specification.
该警告产生于构建完成后的编译过程中,这是因为在idl文件中没有标注该数据结构是否可扩展(非最终版),我们只需要将标注补充一下就行了,比如:
idl
// robot_types.idl
@appendable
module robot {
struct Header {
uint64 sec;
uint32 nsec;
uint32 seq;
};
@appendable
struct ImuRaw {
Header header;
float ax; float ay; float az;
float gx; float gy; float gz;
};
@final
struct MotorPwm {
Header header;
float left;
float right;
};
};