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 |
opaque |
uint64_t |
<stdint.h> |
struct |
struct |
|
union |
union |
|
fmq_sync |
MQDescriptorSync |
libhidlbase |
fmq_unsync |
MQDescriptorUnsync |
libhidlbase |
The sections below describe data types in more detail.
An enum in HIDL becomes an enum in C++. For example:
enum Mode : uint8_t { WRITE = 1 << 0, READ = 1 << 1 };
… becomes:
enum class Mode : uint8_t { WRITE = 1, READ = 2 };
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
.
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 will be overloaded (two prototypes will be generated) to accept and
pass both the HIDL struct and a std::vector<T>
type for that
parameter.
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]
.
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:
std::string
or a C string.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.
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.
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 may 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:
setTo(native_handle_t* handle, bool
shouldOwn)
method with the shouldOwn
parameter set to
true
hidl_handle
object is created by copy construction
from another hidl_handle
objecthidl_handle
object is copy-assigned from another
hidl_handle
objecthidl_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 will automatically take 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 will be valid in that process, it will not persist beyond the receiving
function (it will be closed once 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.
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:
IAllocator
(currently only instance
"ashmem" is available) and use it to allocate shared memory.IAllocator::allocate()
returns a hidl_memory
object that can be passed through HIDL RPC and be mapped into a process using
libhidlmemory
's mapMemory
function.mapMemory
returns a reference to an
sp<IMemory>
object that can be used to access the memory.
(IMemory
and IAllocator
are defined in
android.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();
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 will be defined
as a type.
Variables that hold Interfaces should be strong pointers:
sp<IName>
. HIDL functions that take interface parameters
will convert raw pointers to strong pointers, causing non-intuitive behavior
(the pointer can be cleared unexpectedly). To avoid problems, always store HIDL
interfaces as a sp<>
.