HIDL data declarations generate C++ standard-layout data structures. These structures can be placed anywhere that feels natural (on the stack, at file or global scope, or on the heap) and can be composed in the same fashion. Client code calls HIDL proxy code passing in const references and primitive types, while the stub and proxy code hides the details of serialization.
Note: At no point is developer-written code required to explicitly serialize or deserialize data structures.
The table below maps HIDL primitives to C++ data types:
| HIDL type | C++ Type | Header/library |
|---|---|---|
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 |
The sections below describe data types in more detail.
enum
An enum in HIDL becomes an enum in C++. For example:
enum Mode : uint8_t { WRITE = 1 << 0, READ = 1 << 1 }; enum SpecialMode : Mode { NONE = 0, COMPARE = 1 << 2 };
… becomes:
enum class Mode : uint8_t { WRITE = 1, READ = 2 }; enum class SpecialMode : uint8_t { WRITE = 1, READ = 2, NONE = 0, COMPARE = 4 };
Starting in Android 10, an enum can be iterated
over using ::android::hardware::hidl_enum_range. This range
includes every enumerator in the order it appears in HIDL source code, starting
from the parent enum down to the last child. For example, this code iterates
over WRITE, READ, NONE, and
COMPARE in that order. Given SpecialMode above:
template <typename T> using hidl_enum_range = ::android::hardware::hidl_enum_range<T> for (SpecialMode mode : hidl_enum_range<SpecialMode>) {...}
hidl_enum_range also implements reverse iterators and can be
used in constexpr contexts. If a value appears in an enumeration
multiple times, the value appears in the range multiple times.
bitfield<T>
bitfield<T> (where T is a user-defined enum)
becomes the underlying type of that enum in C++. In the above example,
bitfield<Mode> becomes uint8_t.
vec<T>
The hidl_vec<T> class template is part of
libhidlbase and can be used to pass a vector of any HIDL type with
an arbitrary size. The comparable fixed size container is
hidl_array. A hidl_vec<T> can also be
initialized to point to an external data buffer of type T, using
the hidl_vec::setToExternal() function.
In addition to emitting/inserting the struct appropriately in the generated
C++ header, the use of vec<T> generates some convenience
functions to translate to/from std::vector and bare T
pointers. If the vec<T> is used as a parameter, the function
using it's overloaded (two prototypes are generated) to accept and
pass both the HIDL struct and a std::vector<T> type for that
parameter.
array
Constant arrays in hidl are represented by the hidl_array class
in libhidlbase. A hidl_array<T, S1, S2, …,
SN> represents an N dimensional fixed size array
T[S1][S2]…[SN].
string
The hidl_string class (part of libhidlbase) can be
used to pass strings over HIDL interfaces and is defined in
/system/libhidl/base/include/hidl/HidlSupport.h. The first storage
location in the class is a pointer to its character buffer.
hidl_string knows how to convert to and from
std::string and char* (C-style string) using
operator=, implicit casts, and .c_str() function.
HIDL string structs has the appropriate copy constructors and assignment
operators to:
- Load the HIDL string from an
std::stringor a C string. - Create a new
std::stringfrom a HIDL string.
In addition, HIDL strings have conversion constructors so C strings
(char *) and C++ strings (std::string) can be used on
methods that take a HIDL string.
struct
A struct in HIDL can contain only fixed-size data types and no
functions. HIDL struct definitions map directly to standard-layout
structs in C++, ensuring that structs have a
consistent memory layout. A struct can include HIDL types, including
handle, string, and vec<T>, that
point to separate variable-length buffers.
handle
WARNING: Addresses of any kind (even physical device addresses) must never be part of a native handle. Passing this information between processes is dangerous and makes them susceptible to attack. Any values passed between processes must be validated before being used to look up allocated memory within a process. Otherwise, bad handles can cause bad memory access or memory corruption.
The handle type is represented by the hidl_handle
structure in C++, which is a simple wrapper around a pointer to a
const native_handle_t object (this has been present in Android for
a long time).
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;
By default, hidl_handle does not take ownership
of the native_handle_t pointer it wraps. It merely exists to safely
store a pointer to a native_handle_t such that it can be used in
both 32- and 64-bit processes.
Scenarios in which the hidl_handle does own its enclosed file
descriptors include:
- Following a call to the
setTo(native_handle_t* handle, bool shouldOwn)method with theshouldOwnparameter set totrue - When the
hidl_handleobject is created by copy construction from anotherhidl_handleobject - When the
hidl_handleobject is copy-assigned from anotherhidl_handleobject
hidl_handle provides both implicit and explicit conversions
to/from native_handle_t* objects. The main use for the
handle type in HIDL is to pass file descriptors over HIDL
interfaces. A single file descriptor is therefore represented by a
native_handle_t with no ints and a single
fd. If the client and server live in a different process, the RPC
implementation automatically takes care of the file descriptor to ensure
both processes can operate on the same file.
Although a file descriptor received in a hidl_handle by a
process is valid in that process, it doesn't persist beyond the receiving
function (it's closed when the function returns). A process that wants to
retain persistent access to the file descriptor must dup() the
enclosed file descriptors, or copy the entire hidl_handle object.
memory
The HIDL memory type maps to the hidl_memory class
in libhidlbase, which represents unmapped shared memory. This is
the object that must be passed between processes to share memory in HIDL. To
use shared memory:
- Obtain an instance of
IAllocator(currently only instance "ashmem" is available) and use it to allocate shared memory. IAllocator::allocate()returns ahidl_memoryobject that can be passed through HIDL RPC and be mapped into a process usinglibhidlmemory'smapMemoryfunction.mapMemoryreturns a reference to ansp<IMemory>object that can be used to access the memory. (IMemoryandIAllocatorare defined inandroid.hidl.memory@1.0.)
An instance of IAllocator can be used to allocate memory:
#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 }));
Actual changes to the memory must be done through an IMemory
object, either on the side that created mem or on the side that
receives it over 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
Interfaces can be passed as objects. The word interface can be used
as syntactic sugar for the type android.hidl.base@1.0::IBase;
in addition, the current interface and any imported interfaces are defined
as a type.
Variables that hold Interfaces should be strong pointers:
sp<IName>. HIDL functions that take interface parameters
convert raw pointers to strong pointers, causing nonintuitive behavior
(the pointer can be cleared unexpectedly). To avoid problems, always store HIDL
interfaces as a sp<>.