数据类型

本部分介绍了 HIDL 数据类型。如需了解实现详情,请参阅 HIDL C++(用于 C++ 实现)或 HIDL Java(用于 Java 实现)。

与 C++ 的相似之处包括:

  • structs 使用 C++ 语法;unions 默认支持 C++ 语法。两者都必须命名;不支持匿名结构体和联合体。
  • HIDL 中允许使用 typedef(与 C++ 中一样)。
  • 允许使用 C++ 样式注释,并且这些注释会复制到生成的头文件中。

与 Java 的相似之处包括:

  • 对于每个文件,HIDL 都会定义一个 Java 样式的命名空间,该命名空间必须以 android.hardware. 开头。生成的 C++ 命名空间为 ::android::hardware::…
  • 文件的所有定义都包含在 Java 样式的 interface 封装容器中。
  • HIDL 数组声明遵循 Java 样式,而不是 C++ 样式。示例:
    struct Point {
        int32_t x;
        int32_t y;
    };
    Point[3] triangle;   // sized array
  • 注释类似于 javadoc 格式。

数据表示法

标准布局(普通旧数据类型要求的子集)组成的 structunion 在生成的 C++ 代码中具有一致的内存布局,这通过 structunion 成员上的显式对齐属性来强制执行。

原始 HIDL 类型以及 enumbitfield 类型(它们总是派生自原始类型)映射到标准 C++ 类型,例如来自 cstdintstd::uint32_t

由于 Java 不支持无符号类型,因此无符号 HIDL 类型映射到相应的有符号 Java 类型。Structs 映射到 Java 类;arrays 映射到 Java 数组;Java 目前不支持 unionsStrings 在内部存储为 UTF8。由于 Java 仅支持 UTF16 字符串,因此发送到 Java 实现或从 Java 实现发送的字符串值会被翻译,并且重新翻译后可能不完全相同,因为字符集并非总是能平滑映射。

在 C++ 中通过 IPC 接收的数据被标记为 const,并且位于只读内存中,该内存仅在函数调用期间持续存在。在 Java 中通过 IPC 接收的数据已复制到 Java 对象中,因此可以保留而无需额外复制(并且可以修改)。

注解

Java 风格的注解可以添加到类型声明中。注解由 HIDL 编译器的供应商测试套件 (VTS) 后端解析,但 HIDL 编译器实际上并不理解任何此类已解析的注解。相反,已解析的 VTS 注解由 VTS 编译器 (VTSC) 处理。

注解使用 Java 语法:@annotation@annotation(value)@annotation(id=value, id=value…),其中 value 可能是常量表达式、字符串或 {} 内的值列表,就像在 Java 中一样。可以向同一项附加多个同名注解。

前向声明

在 HIDL 中,结构体可能无法前向声明,这使得用户定义的自引用数据类型成为不可能(例如,您无法在 HIDL 中描述链表或树)。大多数现有(Android 8.x 之前)HAL 对前向声明的使用有限,可以通过重新排列数据结构声明来删除这些声明。

此限制允许通过简单的深层复制按值复制数据结构,而不是跟踪可能在自引用数据结构中多次出现的指针值。如果同一数据被传递两次,例如使用两个方法参数或指向相同数据的 vec<T>,则会创建并传递两个单独的副本。

嵌套声明

HIDL 支持所需级别的嵌套声明(但有一个例外,如下所述)。例如

interface IFoo {
    uint32_t[3][4][5][6] multidimArray;

    vec<vec<vec<int8_t>>> multidimVector;

    vec<bool[4]> arrayVec;

    struct foo {
        struct bar {
            uint32_t val;
        };
        bar b;
    }
    struct baz {
        foo f;
        foo.bar fb; // HIDL uses dots to access nested type names
    }
    

例外情况是接口类型只能嵌入到 vec<T> 中,并且只能嵌套一层深度(不能使用 vec<vec<IFoo>>)。

原始指针语法

HIDL 语言不使用 *,也不支持 C/C++ 原始指针的完整灵活性。有关 HIDL 如何封装指针和数组/向量的详细信息,请参阅 vec<T> 模板

接口

interface 关键字有两种用法。

  • 它在 .hal 文件中打开接口的定义。
  • 它可以作为结构体/联合字段、方法参数和返回中的特殊类型使用。它被视为通用接口,是 android.hidl.base@1.0::IBase 的同义词。

例如,IServiceManager 具有以下方法

get(string fqName, string name) generates (interface service);

该方法承诺按名称查找某些接口。它也等同于用 android.hidl.base@1.0::IBase 替换 interface。

接口只能通过两种方式传递:作为顶级参数,或作为 vec<IMyInterface> 的成员。它们不能是嵌套 vec、结构体、数组或联合的成员。

MQDescriptorSync 和 MQDescriptorUnsync

MQDescriptorSyncMQDescriptorUnsync 类型在 HIDL 接口之间传递同步或非同步快速消息队列 (FMQ) 描述符。有关详细信息,请参阅 HIDL C++(Java 不支持 FMQ)。

memory 类型

memory 类型用于表示 HIDL 中未映射的共享内存。它仅在 C++ 中受支持。可以在接收端使用此类型的值来初始化 IMemory 对象,从而映射内存并使其可用。有关详细信息,请参阅 HIDL C++

警告:放置在共享内存中的结构化数据必须是类型格式在传递 memory 的接口版本的生命周期内永不更改的类型。否则,HAL 可能会遭受严重的兼容性问题。

pointer 类型

pointer 类型仅供 HIDL 内部使用。

bitfield<T> 类型模板

bitfield<T>,其中 T用户定义的枚举,表示该值是 T 中定义的枚举值的按位 OR。在生成的代码中,bitfield<T> 显示为 T 的底层类型。例如

enum Flag : uint8_t {
    HAS_FOO = 1 << 0,
    HAS_BAR = 1 << 1,
    HAS_BAZ = 1 << 2
};
typedef bitfield<Flag> Flags;
setFlags(Flags flags) generates (bool success);

编译器将类型 Flags 与 uint8_t 同等对待。

为什么不使用 (u)int8_t/(u)int16_t/(u)int32_t/(u)int64_t?使用 bitfield 向读者提供了额外的 HAL 信息,现在读者知道 setFlags 接受 Flag 的按位 OR 值(即知道使用 16 调用 setFlags 是无效的)。如果没有 bitfield,此信息只能通过文档传达。此外,VTS 实际上可以检查标志的值是否是 Flag 的按位 OR。

原始类型句柄

警告:任何类型的地址(甚至是物理设备地址)都绝不能成为本机句柄的一部分。在进程之间传递此信息是危险的,并使它们容易受到攻击。在进程之间传递的任何值都必须在用于查找进程内分配的内存之前进行验证。否则,错误的句柄可能会导致错误的内存访问或内存损坏。

HIDL 语义是按值复制,这意味着参数会被复制。任何大型数据块或需要在进程之间共享的数据(例如同步栅栏)都通过传递指向持久对象的文件描述符来处理:ashmem 用于共享内存,实际文件或可以隐藏在文件描述符后面的任何其他内容。binder 驱动程序将文件描述符复制到另一个进程中。

native_handle_t

Android 支持 native_handle_t,这是一种在 libcutils 中定义的通用句柄概念。

typedef struct native_handle
{
  int version;        /* sizeof(native_handle_t) */
  int numFds;         /* number of file-descriptors at &data[0] */
  int numInts;        /* number of ints at &data[numFds] */
  int data[0];        /* numFds + numInts ints */
} native_handle_t;

本机句柄是整数和文件描述符的集合,它们通过值传递。单个文件描述符可以存储在没有整数和单个文件描述符的本机句柄中。使用 handle 原始类型封装的本机句柄传递句柄,可确保本机句柄直接包含在 HIDL 中。

由于 native_handle_t 具有可变大小,因此不能直接包含在结构体中。句柄字段生成指向单独分配的 native_handle_t 的指针。

在早期版本的 Android 中,本机句柄是使用 libcutils 中存在的相同函数创建的。在 Android 8.0 及更高版本中,这些函数现在已复制到 android::hardware::hidl 命名空间或移动到 NDK。HIDL 自动生成的代码会自动序列化和反序列化这些函数,而无需用户编写的代码参与。

句柄和文件描述符所有权

当您调用传递(或返回)hidl_handle 对象(顶级或复合类型的一部分)的 HIDL 接口方法时,其中包含的文件描述符的所有权如下:

  • 作为参数传递 hidl_handle 对象的调用者保留其包装的 native_handle_t 中包含的文件描述符的所有权;当调用者完成使用这些文件描述符时,必须关闭它们。
  • 返回 hidl_handle 对象(通过将其传递到 _cb 函数中)的进程保留对象包装的 native_handle_t 中包含的文件描述符的所有权;当进程完成使用这些文件描述符时,必须关闭它们。
  • 接收 hidl_handle传输拥有对象包装的 native_handle_t 内的文件描述符的所有权;接收者可以在事务回调期间按原样使用这些文件描述符,但必须克隆本机句柄才能在回调之外使用文件描述符。传输在事务完成后自动调用 close() 来关闭文件描述符。

HIDL 不支持 Java 中的句柄(因为 Java 根本不支持句柄)。

定大小数组

对于 HIDL 结构体中的定大小数组,它们的元素可以是结构体可以包含的任何类型

struct foo {
uint32_t[3] x; // array is contained in foo
};

字符串

字符串在 C++ 和 Java 中显示不同,但底层传输存储类型是 C++ 结构。有关详细信息,请参阅 HIDL C++ 数据类型HIDL Java 数据类型

注意:通过 HIDL 接口(包括 Java 到 Java)传递字符串到 Java 或从 Java 传递字符串会导致字符集转换,这可能无法保留原始编码。

vec<T> 类型模板

vec<T> 模板表示包含 T 实例的可变大小缓冲区。

T 可以是以下之一

  • 原始类型(例如 uint32_t)
  • 字符串
  • 用户定义的枚举
  • 用户定义的结构体
  • 接口,或 interface 关键字(vec<IFoo>vec<interface> 仅作为顶级参数受支持)
  • 句柄
  • bitfield<U>
  • vec<U>,其中 U 在此列表中,但接口除外(例如 vec<vec<IFoo>> 不受支持)
  • U[](U 的定大小数组),其中 U 在此列表中,但接口除外

用户定义的类型

本节介绍用户定义的类型。

枚举

HIDL 不支持匿名枚举。否则,HIDL 中的枚举与 C++11 类似

enum name : type { enumerator , enumerator = constexpr ,   }

基本枚举是根据 HIDL 中的整数类型之一定义的。如果未为基于整数类型的枚举的第一个枚举器指定值,则该值默认为 0。如果未为后面的枚举器指定值,则该值默认为前一个值加一。例如

// RED == 0
// BLUE == 4 (GREEN + 1)
enum Color : uint32_t { RED, GREEN = 3, BLUE }

枚举也可以从先前定义的枚举继承。如果未为子枚举(在本例中为 FullSpectrumColor)的第一个枚举器指定值,则该值默认为父枚举的最后一个枚举器的值加一。例如

// ULTRAVIOLET == 5 (Color:BLUE + 1)
enum FullSpectrumColor : Color { ULTRAVIOLET }

警告:枚举继承的工作方式与其他大多数类型的继承相反。子枚举值不能用作父枚举值。这是因为子枚举包含的值比父枚举多。但是,父枚举值可以安全地用作子枚举值,因为子枚举值根据定义是父枚举值的超集。在设计接口时请记住这一点,因为这意味着引用父枚举的类型无法在接口的后续迭代中引用子枚举。

枚举值使用冒号语法(而不是嵌套类型的点语法)引用。语法为 Type:VALUE_NAME。如果在同一枚举类型或子类型中引用该值,则无需指定类型。示例

enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 };
enum Color : Grayscale { RED = WHITE + 1 };
enum Unrelated : uint32_t { FOO = Color:RED + 1 };

从 Android 10 开始,枚举具有 len 属性,该属性可用于常量表达式。MyEnum::len 是该枚举中条目的总数。这与值的总数不同,当值重复时,值的总数可能会更小。

结构体

HIDL 不支持匿名结构体。否则,HIDL 中的结构体与 C 非常相似。

HIDL 不支持完全包含在结构体中的可变长度数据结构。这包括有时用作 C/C++ 中结构体最后一个字段的不定长度数组(有时以大小 [0] 出现)。HIDL vec<T> 表示动态大小的数组,数据存储在单独的缓冲区中;此类实例用 struct 中的 vec<T> 实例表示。

类似地,string 可以包含在 struct 中(关联的缓冲区是单独的)。在生成的 C++ 中,HIDL 句柄类型的实例通过指向实际本机句柄的指针表示,因为底层数据类型的实例是可变长度的。

联合

HIDL 不支持匿名联合。否则,联合与 C 类似。

联合不能包含修复类型(例如指针、文件描述符、binder 对象)。它们不需要特殊字段或关联类型,只需使用 memcpy() 或等效方法进行复制。联合可能不会直接包含(或使用其他数据结构包含)任何需要设置 binder 偏移量的内容(即句柄或 binder 接口引用)。例如

union UnionType {
uint32_t a;
//  vec<uint32_t> r;  // Error: can't contain a vec<T>
uint8_t b;1
};
fun8(UnionType info); // Legal

联合也可以在结构体内部声明。例如

struct MyStruct {
    union MyUnion {
      uint32_t a;
      uint8_t b;
    }; // declares type but not member

    union MyUnion2 {
      uint32_t a;
      uint8_t b;
    } data; // declares type but not member
  }