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 源代码中的顺序迭代,从父枚举开始,直到最后一个子枚举。例如,此代码按这样的顺序迭代 WRITE
、READ
、NONE
和 COMPARE
。仍以上面的 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::vector
和 T
裸指针来回进行转换。如果将 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 类型(包括 handle
、string
和 vec<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 接口传递文件描述符。因此,单个文件描述符由没有 int
的 native_handle_t
和单个 fd
表示。如果客户端和服务器在不同的进程中运行,RPC 实现将自动处理文件描述符,以确保这两个进程可对同一个文件执行操作。
尽管由某个进程在 hidl_handle
中接收的文件描述符在该进程中有效,但它在超出接收函数范围后不会持续有效(它将在该函数返回后关闭)。想要持续访问文件描述符,进程必须对封装的文件描述符执行 dup()
操作,或复制整个 hidl_handle
对象。
内存
HIDL memory
类型会映射到 libhidlbase
中的 hidl_memory
类,该类表示未映射的共享内存。如需在 HIDL 中共享内存,则必须在进程之间传递此对象。为了使用共享内存,需满足以下条件:
- 获取
IAllocator
的实例(当前只有“ashmem”实例可用),并使用该实例分配共享内存。 IAllocator::allocate()
返回hidl_memory
对象,该对象可通过 HIDL RPC 传递,并能使用libhidlmemory
的mapMemory
函数映射到某个进程。mapMemory
返回对可用于访问内存的sp<IMemory>
对象的引用。(IMemory
和IAllocator
在android.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<>
。