FIDL 传输格式规范

如需详细了解 FIDL 的总体用途、目标和要求,请参阅概念概览

核心概念

消息

FIDL 消息是一组数据的集合。

消息是一个连续的结构,由单个内嵌主要对象后跟零个或多个外接次要对象组成。

对象按遍历顺序存储,并适用内边距

绘图

主要对象和次要对象

第一个对象称为主要对象。 它是一个具有固定大小的结构,其类型和大小可从上下文中获知。读取消息时,需要知道要读取的预期类型,即格式不是自描述形式。发生读取的上下文应明确这一点。例如,在作为 IPC 的一部分读取消息(请参阅事务性消息标头)时,上下文完全由标头中包含的数据指定(特别是,序数可让接收者知道所需的类型)。在读取静态数据的情况下,不存在等效描述符,但前提是编码器和解码器都了解正在编码或解码的类型(例如,此信息会编译到编码器和解码器使用的相应库中)。

如果需要其他大小可变的数据或可选数据,主要对象可以引用次要对象(例如对于字符串、矢量、联合体等)。

次要对象以遍历顺序在外行存储。

主要对象和次要对象均按 8 字节对齐,并且存储起来没有间隙(对齐所需的数量除外)。

主要对象及其辅助对象统称为消息

交易消息

事务性 FIDL 消息(事务性消息)用于将数据从一个应用发送到另一个应用。

事务性消息部分介绍了事务性消息如何由标头消息组成,后跟正文消息。

遍历顺序

消息的遍历顺序由其包含的所有对象的递归深度优先遍历确定,这一过程是通过遵循引用链来获取的。

假定采用以下结构:

type Cart = struct {
    items vector<Item>;
};

type Item = struct {
    product Product;
    quantity uint32;
};

type Product = struct {
    sku string;
    name string;
    description string:optional;
    price uint32;
};

Cart 消息的深度优先遍历顺序由以下伪代码定义:

visit Cart:
    for each Item in Cart.items vector data:
        visit Item.product:
            visit Product.sku
            visit Product.name
            visit Product.description
            visit Product.price
        visit Item.quantity

两种形式:编码与解码

相同的消息内容可以采用以下两种形式之一表示:编码和解码。它们具有相同的大小和整体布局,但在指针(内存地址)或句柄(功能)的表示方面有所不同。

FIDL 经过专门设计,可以在内存中对消息进行编码解码

消息编码是规范的,即给定消息正好有一种编码。

绘图

编码消息

已编码的消息已做好传输至其他进程的准备:它不包含指针(内存地址)或句柄(功能)。

编码期间...

  • 消息中指向子对象的所有指针都替换为标志,以表明它们的参照项是否存在。
  • 消息中的所有句柄都会提取到关联的句柄矢量中,并替换为用于指示其指示符是否存在的标志。

然后,您可以使用 zx_channel_write() 或类似 IPC 机制将生成的已编码消息处理矢量发送给其他进程。此类 IPC 还有其他限制。请参阅事务性消息

已解码的消息

解码的消息已准备好在进程的地址空间中使用:它可能包含指针(内存地址)或句柄(功能)。

解码期间:

  • 指向消息中子对象的所有指针均使用经过编码的“存在”和“不存在”标志重建。
  • 消息中的所有句柄都使用编码的存在标记和不存在标记从关联的句柄矢量中恢复。

生成的已解码消息可以直接从内存使用。

内联对象

对象还可能包含内嵌对象,这些对象在所属对象的正文中聚合,例如嵌入式结构体和大小固定的结构体数组。

示例

在以下示例中,Region 结构包含一个 Rect 结构的向量,其中每个 Rect 由两个 Point 组成。每个 Point 均由 xy 值组成。

type Region = struct {
    rects vector<Rect>;
};

type Rect = struct {
    top_left Point;
    bottom_right Point;
};

type Point = struct {
    x uint32;
    y uint32;
};

按遍历顺序检查对象意味着我们从 Region 结构开始,它是主要对象。

rects 成员是 vector,因此其内容是离线存储的。也就是说,vector 内容紧跟在 Region 对象后面。

每个 Rect 结构体都包含两个以内嵌方式存储的 Point(因为其数量是固定的),而 Point 的每个原始数据类型(xy)也是以内嵌方式存储的。道理是一样的;成员类型是固定的。

绘图

当从属对象的大小固定时,我们会使用内嵌存储;如果从属对象的大小是固定的(包括盒式结构体),我们会使用离线存储。

类型详情

在本部分中,我们将说明所有 FIDL 对象的编码。

原语

  • 小端字节序格式存储的值。
  • 按自然对齐方式打包。
    • 每个 m 字节的基元都存储在 m 字节边界上。
  • 非可选项。

支持以下基元类型:

类别 类型
布尔值 bool
有符号整数 int8int16int32int64
无符号整数 uint8uint16uint32uint64
IEEE 754 floating-point float32float64
字符串 (非基元,请参阅字符串

数字类型以位为单位作为后缀。

布尔值类型 bool 存储为单个字节,并且只有值 01

所有浮点值均表示有效的 IEEE 754 位模式。

绘图

绘图

枚举和位

位字段和枚举会存储为其基础基元类型(例如,uint32).

句柄

句柄是一个 32 位整数,但会进行特殊处理。当针对传输进行编码时,句柄的在线表示形式会被替换为存在状态和不存在状态的指示,并且句柄本身会存储在单独的句柄矢量中。解码后,句柄存在状态指示将替换为零(如果不存在)或有效的句柄(如果存在)。

句柄“值”本身不会从一个应用传输到另一个应用。

在这方面,句柄类似于指针;它们引用每个应用独有的上下文。句柄会从一个应用的上下文移至另一个应用的上下文。

绘图

值 0 可用于表示没有可选的句柄1

如需查看通过 FIDL 传输句柄的详细示例,请参阅句柄的生命周期

聚合对象

聚合对象充当其他对象的容器。 他们可能会以内嵌或外行方式存储这类数据,具体取决于其类型。

数组

  • 同构元素固定长度序列。
  • 以自然对齐的方式打包。
    • 数组的对齐方式与其元素的对齐方式相同。
    • 每个后续元素都会在元素的对齐边界上对齐。
  • 该数组的步长与元素的大小(包括满足元素对齐约束条件所需的内边距)完全一致。
  • 一律不可选。
  • Bool 数组没有特殊情况。每个布尔值元素照常占用一个字节。

数组表示为:

  • array<T, N>:其中 T 可以是任何 FIDL 类型(包括数组),N 是数组中的元素数量。注意:数组的大小不得超过 232-1。 如需了解更多详情,请参阅 RFC-0059

绘图

矢量

  • 同构元素的可变长度序列。
  • 可以是可选;不存在的矢量和空矢量是不同的。
  • 可以指定最大大小,例如,vector<T>:40 表示最多 40 个元素矢量。
  • 存储为一条 16 字节的记录,其中包含:
    • size:64 位无符号元素数 注意:矢量的大小不得超过 232-1。 如需了解更多详情,请参阅 RFC-0059
    • data:64 位存在状态指示或指向外行元素数据的指针
  • 当针对传输而编码时,data 表示内容存在:
    • 0:矢量不存在
    • UINTPTR_MAX:矢量存在,数据是下一个离线对象
  • 在解码以供使用时,data 是指向内容的指针:
    • 0:矢量不存在
    • <valid pointer>:矢量已存在,数据位于指示的内存地址
  • 布尔值的矢量没有特殊情况。每个布尔值元素照常占用一个字节。

矢量的表示方式如下:

  • vector<T>:元素类型为 T 的必需矢量(如果缺少 data,则会出现验证错误)
  • vector<T>:optional:元素类型为 T 的可选矢量
  • vector<T>:Nvector<T>:<N, optional>:最大长度为 N 个元素的矢量

T 可以是任何 FIDL 类型。

绘图

字符串

字符串作为 uint8 字节的矢量实现,但字节必须为有效的 UTF-8 编码。

结构

结构包含一系列类型化字段。

在内部,结构会有内边距,以便所有成员都符合所有成员的最大对齐要求。在外部,该结构在 8 字节边界上对齐,因此可能包含最终填充以满足该要求。

以下是一些示例。

包含 int32int8 字段的结构体的对齐方式为 4 字节(由于 int32),大小为 8 字节(int8 后面填充 3 字节):

绘图

包含 boolstring 字段的结构体的对齐方式为 8 个字节(由于 string),大小为 24 个字节(bool 后面填充 7 个字节):

绘图

一个包含 bool 和两个 uint8 字段的结构体的对齐位为 1 字节,大小为 3 字节(无填充!):

绘图

结构可以是:

  • 空 - 表示没有字段。这种结构的大小为 1 字节,对齐字节为 1 字节,并且与包含 uint8 且值为 0 的结构完全相同。
  • 必需 - 结构的内容会以内嵌方式存储。
  • 可选 - 结构的内容以外行方式存储,并通过间接引用进行访问。

结构的存储取决于它是否在引用点进行装箱。

  • 非盒装结构:
    • 内容内嵌存储在其所属类型中,这样可以构建非常高效的聚合结构。
    • 结构布局在内联时不会发生变化;系统不会重新打包其字段以填充容器中的间隙。
  • 盒装结构:
    • 内容存储在不换行的位置,可通过间接引用进行访问。
    • 当针对传输进行编码时,存储的引用指示存在结构:
    • 0:不存在引用
    • UINTPTR_MAX:引用存在,结构内容是下一个外行对象
    • 在解码以供使用时,存储的引用是一个指针:
    • 0:不存在引用
    • <valid pointer>:引用存在,结构内容位于指示的内存地址

结构体由其声明的名称(例如 Circle)表示,可以加边框:

  • Point:必需:Point
  • box<Color>:盒装,始终可选 Color

以下示例说明了这一点:

  • 结构布局(顺序、打包和对齐)
  • 必需的结构 (Point)
  • 盒装 (Color)
type Circle = struct {
    filled bool;
    center CirclePoint; // CirclePoint will be stored in-line
    radius float32;
    color box<Color>; // Color will be stored out-of-line
    dashed bool;
};

type CirclePoint = struct {
    x float32;
    y float32;
};

type Color = struct {
    r float32;
    g float32;
    b float32;
};

Color 内容将填充到 8 字节的辅助对象对齐边界。布局详情如下:

绘图

  1. 第一个成员 filled bool 占用一个字节,但由于下一个成员(具有 4 字节对齐要求),因此需要三个字节的填充。
  2. center CirclePoint 成员是必需结构体的示例。因此,其内容(xy 32 位浮点数)是内联的,且整个内容占用 8 个字节。
  3. radius 是一个 32 位项,需要 4 字节对齐。由于下一个可用位置已经位于 4 字节对齐边界上,因此不需要填充。
  4. color box<Color> 成员是盒装结构的示例。由于 color 数据可能存在也可能不存在,因此最有效的处理方法是保留指向结构的指针作为内嵌数据。这样,如果 color 成员确实存在,则指针指向其数据(或者,如果是编码格式,则指示“存在”),并且数据本身在外存储(在 Circle 结构的数据之后)。如果 color 成员不存在,则指针为 NULL(或者以编码格式存储,通过存储零来指示“不存在”)。
  5. dashed bool 不需要任何特殊对齐,因此它会接下来呢。不过,现在我们已到达对象末尾,所有对象都必须按 8 字节对齐。这意味着我们需要额外 7 个字节的填充。
  6. color 的外行数据遵循 Circle 数据结构,包含三个 32 位 float 值(rgb);它们需要 4 字节对齐,因此可以相互跟随,而无需填充。但是,与 Circle 对象一样,我们要求对象本身按 8 字节对齐,因此需要 4 个字节的填充。

总体而言,此结构需要 48 个字节。

不过,通过将 dashed bool 移到 filled bool 之后,可以显著节省空间 2

绘图

  1. 系统会将两个 bool 值“打包”在一起,以便浪费浪费空间。
  2. 内边距已减少至 2 个字节 - 这非常适合添加 16 位值、更多 bool 或 8 位整数。
  3. 请注意,Color 框之后不需要内边距;所有内容都在 8 字节边界上完美对齐。

该结构现在需要 40 个字节。

信封

信封是数据的容器,供表和联合在内部使用。它不支持 FIDL 语言。具有固定的 8 字节格式。

全为零的信包标头称为“零信封”。它用于表示不存在的信封。否则,信封存在,并且其标志的位 0 会指示数据是以内嵌方式还是外联方式存储:

  • 如果设置了位 0,则使用内嵌表示法

绘图

  • 如果未设置位 0,则使用外行表示法

绘图

仅当载荷大小小于等于 4 个字节时,才能设置位 0。仅当信封为零信封或载荷的大小大于 4 个字节时,位 0 才可以取消设置。

指定 num_bytesnum_handles 可让我们跳过未知的信封内容。

num_bytes 将始终是 8 的倍数,因为外联对象采用 8 字节对齐。

表格

  • 由元素数量和指针组成的记录类型。
  • 指针指向一个信封数组,其中每个信封包含一个元素。
  • 每个元素都与一个序数相关联。
  • 序数是依序排列的,缺口会产生空的信封费用,因此不建议采用。

表由其声明名称(例如,Value),且绝不是可选的:

  • Value:必需:Value

以下示例展示了如何根据表的字段布局。

type Value = table {
    1: command int16;
    2: data Circle;
    3: offset float64;
};

绘图

联合体

  • 由序数和信封组成的记录类型。
  • 序数表示成员选择,用 uint64 表示。
  • 每个元素都与一个用户指定的序数相关联。
  • 序数是有序的。与表不同,序数中的间隙不会产生传输格式空间成本。
  • 缺少的可选并集由 0 序数和零包表示。
  • 不允许空联合。

并集由其声明的名称(例如 Value)和可选性表示:

  • Value:必需:Value
  • Value:optional:可选 Value

以下示例显示了如何根据其字段布局联合。

type UnionValue = strict union {
    1: command int16;
    2: data Circle;
    3: offset float64;
};

绘图

事务性消息

事务性消息始终具有标头,后跟可选的正文

标头和正文都是 FIDL 消息(如上文所定义);即一个数据收集集合。

标头采用以下格式:

绘图

  • zx_txid_t txid,事务 ID(32 位)
    • 设置了高位的 txid 已预留给 zx_channel_call() 使用
    • 未设置高位的 txid 已预留以供用户空间使用
    • txid0 值专用于不需要另一方响应的消息。注意:如需详细了解 txid 分配,请参阅 zx_channel_call()
  • uint8[3] flags 且不得通过绑定进行检查。这些标记可用于实现有线格式的软转换。如需查看当前标志定义的说明,请参阅标头标志
  • uint8 magic number - 用于确定两种传输格式是否兼容。
  • uint64 ordinal
    • 零序数无效。
    • 设置了最高有效位的序数会预留,以用于控制消息和将来的扩展。
    • 未设置最高有效位的序数表示方法调用和响应。

事务性消息有三种:

  • 方法请求,
  • 方法响应,以及
  • 事件请求。

在后面的几个示例中,我们将使用以下界面:

type DivisionError = strict enum : uint32 {
    DIVIDE_BY_ZERO = 1;
};

protocol Calculator {
    Add(struct {
        a int32;
        b int32;
    }) -> (struct {
        sum int32;
    });
    Divide(struct {
        dividend int32;
        divisor int32;
    }) -> (struct {
        quotient int32;
        remainder int32;
    }) error DivisionError;
    Clear();
    -> OnError(struct {
        status_code uint32;
    });
};

Add()Divide() 方法展示了方法请求(从客户端发送到服务器)和方法响应(从服务器发送回客户端)。

Clear() 方法是没有正文的方法请求示例。

说其正文“空”是错误的:这意味着 header 后面有 body。使用 Clear() 时,没有 body,只有 header

方法请求消息

接口的客户端会向服务器发送方法请求消息,以便调用该方法。

方法响应消息

服务器向客户端发送方法响应消息,以指示方法调用已完成并提供(可能为空的)结果。

只有在协议声明中定义为提供(可能为空)结果的双向方法请求才会引发方法响应。单向方法请求不得生成方法响应。

方法响应消息提供与先前的方法请求关联的结果。消息的正文包含方法结果,就好像这些结果打包在一个结构体中一样。

在这里我们可以看到,912 / 43 的答案是 21,余数为 9。 请注意 1txid 值,该值用于标识事务。2ordinal 值表示方法,在本例中为 Divide() 方法。

绘图

在下面,我们看到 123 + 456579。此处,txid 值现在为 2 - 这只是分配给事务的下一个事务编号。ordinal1,表示 Add()。请注意,结果需要 4 个字节的填充,才能使 body 对象的大小为 8 个字节的倍数。

绘图

最后,Clear() 方法在两个重要方面不同于 Add()Divide():* 它没有正文(即仅由标头组成),* 它不会从接口征集响应(txid 为零)。

绘图

活动请求

Calculator 中的 OnError() 事件就是一个事件示例。

服务器向客户端发送自发事件请求,以指明发生了异步事件(如协议声明中所指定)。

Calculator 示例中,我们可以假设尝试除以 0 会导致在连接关闭之前发送 OnError() 事件,并返回“除以零”状态代码。这样,客户端就可以区分连接是由于错误而关闭,而不是由于其他原因(例如计算器进程异常终止)。

绘图

请注意,txid 为零(表示这不是事务的一部分),ordinal4(表示 OnError() 方法)。

body 包含事件参数,就像打包在 struct 中一样,就像方法结果消息一样。请注意,正文带有内边距,以保持 8 字节对齐。

书写(控制消息序数 0xFFFFFFFFFFFFFFFF)

epitaph 是序数为 0xFFFFFFFFFFFFFFFFFF 的事件 (txid0)。服务器可以在关闭连接之前发送一条语音描述作为最后一条消息,以说明连接关闭的原因。歌词之后,不能再通过连接发送更多消息。书写并不是从客户端发送到服务器。

圣书节的电线表示法与以下 FIDL 等效: fidl struct { error zx.Status; }; 书信将来可能会在 FIDL 中正式定义。

详细信息

大小和对齐

sizeof(T) 表示 T 类型的对象的大小(以字节为单位)。T

alignof(T) 表示用于存储 T 类型的对象的对齐因子(以字节为单位)。T

FIDL 基元类型存储在消息中的偏移量,即其大小的倍数(以字节为单位)。因此,对于基元 T,需为 alignof(T) == sizeof(T)。这称为自然对齐。它能够满足现代 CPU 架构的典型对齐要求。

FIDL 复杂类型(如结构体和数组)存储在消息中的偏移量,即其所有字段的最大对齐因数的倍数。因此,对于复杂类型 T,对于 T 中的所有字段 Falignof(T) == max(alignof(F:T))。它具有满足典型 C 结构打包要求的不错属性(可以使用生成的代码中的打包属性强制执行)。复杂类型的大小是指存储其成员正确对齐的成员所需的字节总数,加上按类型的对齐因子的内边距。

FIDL 主要对象和辅助对象在消息中按 8 字节的偏移量对齐,无论它们的内容如何。FIDL 消息的主要对象从偏移量 0 开始。次要对象是消息中唯一可能的指针指代,其偏移量始终为 8 的倍数的偏移量。(因此,消息中的所有指针均指向偏移量为 8 的倍数的偏移量)。

FIDL 内嵌对象(嵌入在主要或次要对象中的复杂类型)会根据其类型对齐。不强制将它们对齐 8 字节。

类型

注意:

  • N 表示元素数量,无论是明确声明(如 array<T, N>,包含 T 类型的 N 个元素的数组)还是隐式(由 7 个元素组成的 table 将具有 N=7)。
  • 外行大小始终填充为 8 个字节。我们在下方指明了内容大小,但不包括内边距。
  • 以下 vector 条目中的 sizeof(T)
    in_line_sizeof(T) + out_of_line_sizeof(T)
  • 以下 table 条目中的 M 是当前字段的最大序数。
  • 在下面的 struct 条目中,内边距是指使 struct 与最宽元素对齐所需的内边距。例如,struct{uint32;uint8} 的内边距为 3 个字节,与内边距不同,即对齐至 8 个字节的边界。
类型 大小(内嵌) 大小(外行) 对齐方式
bool 1 0 1
int8uint8 1 0 1
int16uint16 2 0 2
int32uint32float32 4 0 4
int64uint64float64 8 0 8
enumbits (基础类型) 0 (基础类型)
handle 4 0 4
array<T, N> sizeof(T) * N 0 对齐方式(T)
vector 16 N * sizeof(T) 8
struct 总和(sizeof(fields)) + 内边距 0 8
box<struct> 8 总和(sizeof(fields)) + 内边距 8
envelope 8 sizeof(字段) 8
table 16 M * sizeof(信封) +sum(aligned_to_8(sizeof(当前字段)) 8
unionunion:optional 16 sizeof(所选变体) 8

上面的 handle 条目是指所有类型的句柄,具体来讲,有 handlehandle:optionalhandle:Hhandle:<H, optional>client_end:Protocolclient_end:<Protocol, optional>server_end:Protocolserver_end:<Protocol, optional>

同样,上面的 vector 条目是指所有矢量特性,具体来讲,有 vector<T>vector<T>:optionalvector<T>:Nvector<T>:<N, optional>stringstring:optionalstring:Nstring:<N, optional>

内边距

消息的创建者必须用零填充所有对齐内边距间隙。

消息的使用方必须验证填充是否包含零(如果不包含,则生成错误)。

最大递归深度

FIDL 矢量、可选结构、表和联合支持构建递归消息。如果不选中,处理过深的消息可能会导致资源耗尽,或检测不到无限循环。

为了安全起见,所有 FIDL 消息的最大递归深度都限制为 32 级间接。FIDL 编码器、解码器或验证器必须在消息验证期间跟踪当前的递归深度,以强制执行此限制。

递归深度的正式定义:

  • FIDL 消息的内联对象定义为递归深度为 0
  • 每次通过指针或包封遍历时,递归深度都会增加 1

如果任何时候递归深度超过 32,则必须终止操作并引发错误。

例如:

type InlineObject = struct {
    content_a string;
    vector vector<OutOfLineStructAtLevel1>;
    table TableInlineAtLevel0;
};

type OutOfLineStructAtLevel1 = struct {
    content_b string;
};

type TableInlineAtLevel0 = table {
    1: content_c string;
};

InlineObject 的实例进行编码时,我们拥有相应的递归深度:

  • content_a 的字节的递归深度为 1,即 content_a 字符串标头内嵌在 InlineObject 结构体内,而字节位于可通过指针间接访问的外联对象中。
  • content_b 的字节的递归深度为 2,也就是说,vector 标头内嵌在 InlineObject 结构体内,OutOfLineStructAtLevel1 结构体的递归深度为 1,content_b 字符串标头内嵌在 OutOfLineStructAtLevel1 内,而字节位于外联对象中,可通过从深度 1 的指针间接访问来进行访问,因此它们位于深度 2 的深度。
  • content_c 的字节的递归深度为 3,即 table 标头内联在 InlineObject 结构体内,表包封的深度为 1,指向深度为 2 的 content_c 字符串标头,而字节位于可通过指针间接访问的外联对象中,使它们的深度为 3。

验证

消息验证的目的是尽早发现传输格式错误,以免出现安全或稳定性问题。

对从对等端接收的消息进行解码时,为了防止错误数据传播到服务入口点以外,必须进行消息验证。

对要发送给对等方的消息进行编码时,消息验证是可选的,但建议执行,以帮助本地化违反完整性限制的问题。

为了最大限度地减少运行时开销,验证通常应作为单通消息编码或解码过程的一部分执行,以便只需执行一次遍历。由于消息是按深度优先遍历顺序编码的,因此遍历会表现出良好的内存局部性,因此应该非常高效。

对于简单消息,验证可能非常简单,只需检查几个大小即可。虽然我们鼓励程序员依靠 FIDL 绑定库来代表他们验证消息,但也可以根据需要手动验证。

一致的 FIDL 绑定必须检查以下所有完整性约束:

  • 消息(包括其所有外行子对象)的总大小恰好等于包含它的缓冲区的总大小。所有子对象都会计算在内。
  • 消息引用的句柄总数恰好等于句柄表格的总大小。所有标识名都会计算在内。
  • 不超过复杂对象的递归深度上限。
  • 所有枚举值都在其定义的范围内。
  • 所有联合标记值都处于其定义的范围内。
  • 仅编码:
    • 在遍历期间遇到的指向子对象的所有指针均精确指向预计会显示子对象的下一个缓冲区位置。由此推论,指针永远不会引用缓冲区之外的位置。
  • 仅解码:
    • 引用的子对象的所有存在标记和不存在标记都仅保留值 0UINTPTR_MAX
    • 引用的句柄的所有存在标记和不存在标记都只能保留值 0UINT32_MAX

标头标记

Flags[0]

当前用量 过去的使用记录
7(MSB) 未使用
6 未使用
5 未使用
4 未使用
3 未使用
2 未使用
1 指明是否使用 v2 传输格式 (RFC-0114)
0 未使用 指示静态联合是否应编码为 xunion (RFC-0061)

Flags[1]

当前用量 过去的使用记录
7(MSB) 未使用
6 未使用
5 未使用
4 未使用
3 未使用
2 未使用
1 未使用
0 未使用

Flags[2]

当前用量 过去的使用记录
7(MSB) 未使用
6 未使用
5 未使用
4 未使用
3 未使用
2 未使用
1 未使用
0 未使用

  1. 将零句柄定义为“没有任何句柄”意味着,您可以安全地将有线格式结构默认初始化为所有零。零也是 ZX_HANDLE_INVALID 常量的值。

  2. 阅读 The Lost Art of Structure Packing,深入介绍该主题。