Data types

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 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 the shouldOwn parameter set to true
  • When the hidl_handle object is created by copy construction from another hidl_handle object
  • When the hidl_handle object is copy-assigned from another hidl_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 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:

  1. Obtain an instance of IAllocator (currently only instance "ashmem" is available) and use it to allocate shared memory.
  2. 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.
  3. 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();

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<>.