数据类型

HIDL 数据声明可生成 C++ 标准布局数据结构。您可以将这些结构放置在任何合适的位置(可以放在堆栈上,放在文件或全局范围内,也可以放在堆区上),而且这些结构能以相同的方式构成。客户端代码会调用传入常量引用和基元类型的 HIDL 代理代码,而桩和代理代码会隐藏序列化的细节。

注意:在任何情况下,开发者编写的代码都不需要对数据结构进行显式序列化或反序列化。

下表说明 HIDL 基元与 C++ 数据类型之间的对应关系:

HIDL 类型 C++ 类型 头文件/库
enum enum class
uint8_t..uint64_t uint8_t..uint64_t <stdint.h>
int8_t..int64_t int8_t..int64_t <stdint.h>
float float
double double
vec<T> hidl_vec<T> libhidlbase
T[S1][S2]...[SN] T[S1][S2]...[SN]
string hidl_string libhidlbase
handle hidl_handle libhidlbase
safe_union (custom) struct
struct struct
union union
fmq_sync MQDescriptorSync libhidlbase
fmq_unsync MQDescriptorUnsync libhidlbase

下面几部分详细介绍了这些数据类型。

枚举

HIDL 形式的枚举会变为 C++ 形式的枚举。例如:

enum Mode : uint8_t { WRITE = 1 << 0, READ = 1 << 1 };
enum SpecialMode : Mode { NONE = 0, COMPARE = 1 << 2 };

…会变为:

enum class Mode : uint8_t { WRITE = 1, READ = 2 };
enum class SpecialMode : uint8_t { WRITE = 1, READ = 2, NONE = 0, COMPARE = 4 };

从 Android 10 开始,可以使用 ::android::hardware::hidl_enum_range 迭代枚举。此范围包括每个枚举器,按照出现在 HIDL 源代码中的顺序迭代,从父枚举开始,直到最后一个子枚举。例如,此代码按这样的顺序迭代 WRITEREADNONECOMPARE。仍以上面的 SpecialMode 为例:

template <typename T>
using hidl_enum_range = ::android::hardware::hidl_enum_range<T>

for (SpecialMode mode : hidl_enum_range<SpecialMode>) {...}

hidl_enum_range 还能实现反向迭代器,可以在 constexpr 上下文中使用。如果某个值多次出现在枚举中,则该值也会多次出现在该范围内。

bitfield<T>

bitfield<T>(其中 T 是用户定义的枚举)会变为该枚举的 C++ 形式的底层类型。在上面的示例中,bitfield<Mode> 会变为 uint8_t

vec<T>

hidl_vec<T> 类模板是 libhidlbase 的一部分,可用于传递任意大小的任何 HIDL 类型的矢量。与之相当的固定大小的容器是 hidl_array。此外,您也可以使用 hidl_vec::setToExternal() 函数将 hidl_vec<T> 初始化为指向 T 类型的外部数据缓冲区。

除了在生成的 C++ 头文件中适当地发出/插入结构体之外,使用 vec<T> 还能生成一些便捷函数,用于与 std::vectorT 裸指针来回进行转换。如果将 vec<T> 用作参数,使用它的函数将会重载(生成两个原型),以同时接受并传递该参数的 HIDL 结构体和 std::vector<T> 类型。

数组

HIDL 中的常量数组由 libhidlbase 中的 hidl_array 类表示。hidl_array<T, S1, S2, …, SN> 表示固定大小的 N 维数组 T[S1][S2]…[SN]

字符串

hidl_string 类(libhidlbase 的一部分)可用于通过 HIDL 接口传递字符串,它在 /system/libhidl/base/include/hidl/HidlSupport.h 中进行定义。该类中的第一个存储位置是指向其字符缓冲区的指针。

hidl_string 知道如何使用 operator=、隐式类型转换和 .c_str() 函数与 std::string and char*(C 样式的字符串)来回进行转换。HIDL 字符串结构体具有适当的复制构造函数和赋值运算符,可用于:

  • std::string 或 C 字符串加载 HIDL 字符串。
  • 从 HIDL 字符串创建新的 std::string

此外,HIDL 字符串还有转换构造函数,因此 C 字符串 (char *) 和 C++ 字符串 (std::string) 可用于采用 HIDL 字符串的方法。

结构体

HIDL 形式的 struct 只能包含固定大小的数据类型,不能包含任何函数。HIDL 结构体定义会直接映射到 C++ 形式的标准布局 struct,从而确保 struct 具有一致的内存布局。一个结构体可以包含多个指向单独的可变长度缓冲区的 HIDL 类型(包括 handlestringvec<T>)。

句柄

警告:任何类型的地址(哪怕是实体设备地址)都不得是原生句柄的一部分。在进程之间传递该信息很危险,会导致进程容易受到攻击。在进程之间传递的任何值都必须先经过验证,然后才能用于在进程内查找分配的内存。否则,错误的句柄可能会导致内存访问错误或内存损坏。

handle 类型由 C++ 形式的 hidl_handle 结构表示,该结构是一个简单的封装容器,用于封装指向 const native_handle_t 对象的指针(这已经在 Android 中存在了很长时间)。

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;

默认情况下,hidl_handle 对它所封装的 native_handle_t 指针并不具备所有权。它的存在只是为了安全地存储指向 native_handle_t 的指针,以使其在 32 位和 64 位进程中均可使用。

在以下情况下,hidl_handle 会拥有它所封装的文件描述符:

  • 在调用 setTo(native_handle_t* handle, bool shouldOwn) 方法(将 shouldOwn 参数设为 true)后
  • hidl_handle 对象是通过复制其他 hidl_handle 对象的结构创建而成时
  • hidl_handle 对象的赋值是从其他 hidl_handle 对象复制而来时

hidl_handle 可提供与 native_handle_t* 对象来回的隐式和显式转换。HIDL 中 handle 类型的主要用途是通过 HIDL 接口传递文件描述符。因此,单个文件描述符由没有 intnative_handle_t 和单个 fd 表示。如果客户端和服务器在不同的进程中运行,RPC 实现将自动处理文件描述符,以确保这两个进程可对同一个文件执行操作。

尽管由某个进程在 hidl_handle 中接收的文件描述符在该进程中有效,但它在超出接收函数范围后不会持续有效(它将在该函数返回后关闭)。想要持续访问文件描述符,进程必须对封装的文件描述符执行 dup() 操作,或复制整个 hidl_handle 对象。

内存

HIDL memory 类型会映射到 libhidlbase 中的 hidl_memory 类,该类表示未映射的共享内存。如需在 HIDL 中共享内存,则必须在进程之间传递此对象。为了使用共享内存,需满足以下条件:

  1. 获取 IAllocator 的实例(当前只有“ashmem”实例可用),并使用该实例分配共享内存。
  2. IAllocator::allocate() 返回 hidl_memory 对象,该对象可通过 HIDL RPC 传递,并能使用 libhidlmemorymapMemory 函数映射到某个进程。
  3. mapMemory 返回对可用于访问内存的 sp<IMemory> 对象的引用。(IMemoryIAllocatorandroid.hidl.memory@1.0 中进行定义。)

IAllocator 的实例可用于分配内存:

#include <android/hidl/allocator/1.0/IAllocator.h>
#include <android/hidl/memory/1.0/IMemory.h>
#include <hidlmemory/mapping.h>
using ::android::hidl::allocator::V1_0::IAllocator;
using ::android::hidl::memory::V1_0::IMemory;
using ::android::hardware::hidl_memory;
....
  sp<IAllocator> ashmemAllocator = IAllocator::getService("ashmem");
  ashmemAllocator->allocate(2048, [&](bool success, const hidl_memory& mem) {
        if (!success) { /* error */ }
        // now you can use the hidl_memory object 'mem' or pass it around
  }));

对内存的实际更改必须通过 IMemory 对象完成(要么在创建 mem 的一端完成,要么在通过 HIDL RPC 接收它的一端完成)。

// Same includes as above

sp<IMemory> memory = mapMemory(mem);
void* data = memory->getPointer();
memory->update();
// update memory however you wish after calling update and before calling commit
data[0] = 42;
memory->commit();
// …
memory->update(); // the same memory can be updated multiple times
// …
memory->commit();

接口

接口可作为对象传递。“接口”一词可用作 android.hidl.base@1.0::IBase 类型的语法糖;此外,当前的接口以及任何导入的接口都将被定义为一个类型。

持有接口的变量应该是强指针:sp<IName>。接受接口参数的 HIDL 函数会将原始指针转换为强指针,从而导致非直观的行为(可能会意外清除指针)。为避免出现问题,请务必将 HIDL 接口存储为 sp<>