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 源代码中出现的每个枚举器,从父枚举到最后一个子枚举。 例如,以下代码按顺序迭代 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<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 类型,包括指向单独的可变长度缓冲区的 handle
、string
和 vec<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 接口传递文件描述符。 因此,单个文件描述符由一个没有 int
s 和单个 fd
的 native_handle_t
表示。 如果客户端和服务器位于不同的进程中,则 RPC 实现会自动处理文件描述符,以确保两个进程都可以对同一文件进行操作。
虽然进程在 hidl_handle
中接收到的文件描述符在该进程中有效,但它不会在接收函数之外持续存在(函数返回时会关闭)。 想要保持对文件描述符的持久访问的进程必须 dup()
封闭的文件描述符,或复制整个 hidl_handle
对象。
memory
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();
interface
接口可以作为对象传递。 interface 一词可以用作类型 android.hidl.base@1.0::IBase
的语法糖;此外,当前接口和任何导入的接口都定义为一种类型。
保存接口的变量应该是强指针:sp<IName>
。 接受接口参数的 HIDL 函数将原始指针转换为强指针,从而导致违反直觉的行为(指针可能会意外清除)。 为避免出现问题,请始终将 HIDL 接口存储为 sp<>
。