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::string
or a C string. - Create a new
std::string
from 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
struct
s in C++, ensuring that struct
s 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 theshouldOwn
parameter set totrue
- When the
hidl_handle
object is created by copy construction from anotherhidl_handle
object - When the
hidl_handle
object is copy-assigned from anotherhidl_handle
object
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 int
s 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_memory
object that can be passed through HIDL RPC and be mapped into a process usinglibhidlmemory
'smapMemory
function.mapMemory
returns a reference to ansp<IMemory>
object that can be used to access the memory. (IMemory
andIAllocator
are 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<>
.