数据类型

HIDL 数据声明生成 C++ 标准布局数据结构。 这些结构可以放置在任何自然的位置(堆栈上、文件或全局作用域中,或者在堆上),并且可以以相同的方式组合。 客户端代码调用 HIDL 代理代码,传入 const 引用和原始类型,而桩代码和代理代码隐藏了序列化的细节。

注意: 在任何时候,都不需要开发者编写代码来显式地序列化或反序列化数据结构。

下表将 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 (自定义)struct
struct struct
union union
fmq_sync MQDescriptorSync libhidlbase
fmq_unsync MQDescriptorUnsync libhidlbase

以下部分更详细地描述了数据类型。

enum

HIDL 中的 enum 在 C++ 中变成 enum。 例如

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_arrayhidl_vec<T> 也可以初始化为指向类型为 T 的外部数据缓冲区,使用 hidl_vec::setToExternal() 函数。

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

array

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

string

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

hidl_string 知道如何使用 operator=、隐式转换和 .c_str() 函数与 std::string 和 char* (C 风格字符串)之间进行转换。 HIDL 字符串结构具有适当的复制构造函数和赋值运算符,可以

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

此外,HIDL 字符串具有转换构造函数,因此 C 字符串(char *)和 C++ 字符串(std::string)可以在接受 HIDL 字符串的方法中使用。

struct

HIDL 中的 struct 只能包含固定大小的数据类型,不能包含函数。 HIDL struct 定义直接映射到 C++ 中的标准布局 struct,确保 struct 具有一致的内存布局。 struct 可以包含 HIDL 类型,包括指向单独的可变长度缓冲区的 handlestringvec<T>

handle

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

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 接口传递文件描述符。 因此,单个文件描述符由一个没有 ints 和单个 fdnative_handle_t 表示。 如果客户端和服务器位于不同的进程中,则 RPC 实现会自动处理文件描述符,以确保两个进程都可以对同一文件进行操作。

虽然进程在 hidl_handle 中接收到的文件描述符在该进程中有效,但它不会在接收函数之外持续存在(函数返回时会关闭)。 想要保持对文件描述符的持久访问的进程必须 dup() 封闭的文件描述符,或复制整个 hidl_handle 对象。

memory

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();

interface

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

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