This section describes HIDL data types. For implementation details, see HIDL C++ (for C++ implementations) or HIDL Java (for Java implementations).
Similarities to C++ include:
structs
use C++ syntax;unions
support C++ syntax by default. Both must be named; anonymous structs and unions aren't supported.- Typedefs are allowed in HIDL (as they are in C++).
- C++-style comments are allowed and are copied to the generated header file.
Similarities to Java include:
- For each file, HIDL defines a Java-style namespace that must begin with
android.hardware.
. The generated C++ namespace is::android::hardware::…
. - All definitions of the file are contained within a Java-style
interface
wrapper. - HIDL array declarations follow the Java style, not the C++ style. Example:
struct Point { int32_t x; int32_t y; }; Point[3] triangle; // sized array
- Comments are similar to the javadoc format.
Data representation
A struct
or union
composed of
Standard-Layout
(a subset of the requirement of plain-old-data types) has a consistent memory
layout in generated C++ code, enforced with explicit alignment attributes on
struct
and union
members.
Primitive HIDL types, as well as enum
and bitfield
types (which always derive from primitive types), map to standard C++ types
such as std::uint32_t
from
cstdint.
As Java doesn't support unsigned types, unsigned HIDL types are mapped to the corresponding signed Java type. Structs map to Java classes; arrays map to Java arrays; unions aren't currently supported in Java. Strings are stored internally as UTF8. Since Java supports only UTF16 strings, string values sent to or from a Java implementation are translated, and might not be identical on re-translation as the character sets don't always map smoothly.
Data received over IPC in C++ is marked const
and is in
read-only memory that persists only for the duration of the function call. Data
received over IPC in Java has already been copied into Java objects, so it can
be retained without additional copying (and can be modified).
Annotations
Java-style annotations can be added to type declarations. Annotations are parsed by the Vendor Test Suite (VTS) backend of the HIDL compiler but none of such parsed annotations are actually understood by the HIDL compiler. Instead, parsed VTS annotations are handled by the VTS Compiler (VTSC).
Annotations use Java syntax: @annotation
or
@annotation(value)
or @annotation(id=value, id=value…)
where value might be either a constant expression, a string, or a list of values
inside {}
, just as in Java. Multiple annotations of the same name
can be attached to the same item.
Forward declarations
In HIDL, structs might not be forward declared, making user-defined, self-referential data types impossible (for example, you can't describe a linked list or a tree in HIDL). Most existing (pre-Android 8.x) HALs have limited use of forward declarations, which can be removed by rearranging data structure declarations.
This restriction allows data structures to be copied by-value with a simple
deep-copy, rather than keeping track of pointer values that might occur multiple
times in a self-referential data structure. If the same data is passed twice,
such as with two method parameters or vec<T>
s that point to
the same data, two separate copies are made and delivered.
Nested declarations
HIDL supports nested declarations to as many levels as desired (with one exception noted below). For example:
interface IFoo { uint32_t[3][4][5][6] multidimArray; vec<vec<vec<int8_t>>> multidimVector; vec<bool[4]> arrayVec; struct foo { struct bar { uint32_t val; }; bar b; } struct baz { foo f; foo.bar fb; // HIDL uses dots to access nested type names } …
The exception is that interface types can only be embedded in
vec<T>
and only one level deep (no
vec<vec<IFoo>>
).
Raw pointer syntax
The HIDL language doesn't use * and doesn't support the full flexibility of C/C++ raw pointers. For details on how HIDL encapsulates pointers and arrays/vectors, see vec<T> template.
Interfaces
The interface
keyword has two usages.
- It opens the definition of an interface in a .hal file.
- It can be used as a special type in struct/union fields, method parameters,
and returns. It is viewed as a general interface and synonym to
android.hidl.base@1.0::IBase
.
For example, IServiceManager
has the following method:
get(string fqName, string name) generates (interface service);
The method promises to lookup some interface by name. It is also
identical to replace interface with android.hidl.base@1.0::IBase
.
Interfaces can be only passed in two ways: as top-level parameters, or as
members of a vec<IMyInterface>
. They cannot be members of
nested vecs, structs, arrays, or unions.
MQDescriptorSync and MQDescriptorUnsync
The MQDescriptorSync
and MQDescriptorUnsync
types
pass a synchronized or unsynchronized Fast Message Queue (FMQ) descriptors
across a HIDL interface. For details, see
HIDL C++ (FMQs aren't
supported in Java).
memory type
The memory
type is used to represent unmapped shared memory in
HIDL. It is only supported in C++. A value of this type can be used on the
receiving end to initialize an IMemory
object, mapping the memory
and making it usable. For details, see
HIDL C++.
Warning: Structured data placed in shared
memory MUST be a type whose format never changes for the lifetime of the
interface version passing the memory
. Otherwise, HALs can suffer
fatal compatibility problems.
pointer type
The pointer
type is for HIDL internal use only.
bitfield<T> type template
bitfield<T>
in which T
is a
user-defined enum suggests the value is a bitwise-OR of the
enum values defined in T
. In generated code,
bitfield<T>
appears as the underlying type of T. For
example:
enum Flag : uint8_t { HAS_FOO = 1 << 0, HAS_BAR = 1 << 1, HAS_BAZ = 1 << 2 }; typedef bitfield<Flag> Flags; setFlags(Flags flags) generates (bool success);
The compiler handles the type Flags the same as uint8_t
.
Why not use
(u)int8_t
/(u)int16_t
/(u)int32_t
/(u)int64_t
?
Using bitfield
provides additional HAL information to the reader,
who now knows that setFlags
takes a bitwise-OR value of Flag (i.e.
knows that calling setFlags
with 16 is invalid). Without
bitfield
, this information is conveyed only via documentation. In
addition, VTS can actually check if the value of flags is a bitwise-OR of Flag.
Primitive type handles
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 they are used to look up allocated memory within a process. Otherwise, bad handles might cause bad memory access or memory corruption.
HIDL semantics are copy-by-value, which implies that parameters are copied.
Any large pieces of data, or data that needs to be shared between processes
(such as a sync fence), are handled by passing around file descriptors pointing
to persistent objects: ashmem
for shared memory, actual files, or
anything else that can hide behind a file descriptor. The binder driver
duplicates the file descriptor into the other process.
native_handle_t
Android supports native_handle_t
, a general handle concept
defined in libcutils
.
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;
A native handle is a collection of ints and file descriptors that gets passed
around by value. A single file descriptor can be stored in a native handle with
no ints and a single file descriptor. Passing handles using native handles
encapsulated with the handle
primitive type ensures that native
handles are directly included in HIDL.
As a native_handle_t
has variable size, it cannot be included
directly in a struct. A handle field generates a pointer to a separately
allocated native_handle_t
.
In earlier versions of Android, native handles were created using the same
functions present in
libcutils.
In Android 8.0 and higher, these functions are now copied to the
android::hardware::hidl
namespace or moved to the NDK. HIDL
autogenerated code serializes and deserializes these functions automatically,
without involvement from user-written code.
Handle and file descriptor ownership
When you call a HIDL interface method that passes (or returns) a
hidl_handle
object (either top-level or part of a compound type),
the ownership of the file descriptors contained in it is as follows:
- The caller passing a
hidl_handle
object as an argument retains ownership of the file descriptors contained in thenative_handle_t
it wraps; the caller must close these file descriptors when it is done with them. - The process returning a
hidl_handle
object (by passing it into a_cb
function) retains ownership of the file descriptors contained in thenative_handle_t
wrapped by the object; the process must close these file descriptors when it is done with them. - A transport that receives a
hidl_handle
has ownership of the file descriptors inside thenative_handle_t
wrapped by the object; the receiver can use these file descriptors as is during the transaction callback, but must clone the native handle to use the file descriptors beyond the callback. The transport automatically callsclose()
for the file descriptors when the transaction is done.
HIDL doesn't support handles in Java (as Java doesn't support handles at all).
Sized arrays
For sized arrays in HIDL structs, their elements can be of any type a struct can contain:
struct foo { uint32_t[3] x; // array is contained in foo };
Strings
Strings appear differently in C++ and Java, but the underlying transport storage type is a C++ structure. For details, see HIDL C++ Data Types or HIDL Java Data Types.
Note: Passing a string to or from Java through a HIDL interface (including Java to Java) causes character set conversions that might not preserve the original encoding.
vec<T> type template
The vec<T>
template represents a variable-sized buffer
containing instances of T
.
T
can be one of the following:
- Primitive types (e.g. uint32_t)
- Strings
- User-defined enums
- User-defined structs
- Interfaces, or the
interface
keyword (vec<IFoo>
,vec<interface>
is supported only as a top-level parameter) - Handles
- bitfield<U>
- vec<U>, where U is in this list except interface (e.g.
vec<vec<IFoo>>
isn't supported) - U[] (sized array of U), where U is in this list except interface
User-defined types
This section describes user-defined types.
Enum
HIDL doesn't support anonymous enums. Otherwise, enums in HIDL are similar to C++11:
enum name : type { enumerator , enumerator = constexpr , … }
A base enum is defined in terms of one of the integer types in HIDL. If no value is specified for the first enumerator of an enum based on an integer type, the value defaults to 0. If no value is specified for a later enumerator, the value defaults to the previous value plus one. For example:
// RED == 0 // BLUE == 4 (GREEN + 1) enum Color : uint32_t { RED, GREEN = 3, BLUE }
An enum can also inherit from a previously defined enum. If no value is
specified for the first enumerator of a child enum (in this case
FullSpectrumColor
), it defaults to the value of the last
enumerator of the parent enum plus one. For example:
// ULTRAVIOLET == 5 (Color:BLUE + 1) enum FullSpectrumColor : Color { ULTRAVIOLET }
Warning: Enum inheritance works backwards from most other types of inheritance. A child enum value can't be used as a parent enum value. This is because a child enum includes more values than the parent. However, a parent enum value can be safely used as a child enum value because child enum values are by definition a superset of parent enum values. Keep this in mind when designing interfaces as this means types referring to parent enums can't refer to child enums in later iterations of your interface.
Values of enums are referred to with the colon syntax (not dot syntax as
nested types). The syntax is Type:VALUE_NAME
. No need to specify
type if the value is referenced in the same enum type or child types. Example:
enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 }; enum Color : Grayscale { RED = WHITE + 1 }; enum Unrelated : uint32_t { FOO = Color:RED + 1 };
Starting in Android 10, enums have a
len
attribute that can be used in constant expressions.
MyEnum::len
is the total number of entries in that enumeration.
This is different from the total number of values, which might be smaller when
values are duplicated.
Struct
HIDL doesn't support anonymous structs. Otherwise, structs in HIDL are very similar to C.
HIDL doesn't support variable-length data structures contained wholly within
a struct. This includes the indefinite-length array that is sometimes used as
the last field of a struct in C/C++ (sometimes seen with a size of
[0]
). HIDL vec<T>
represents dynamically-sized
arrays with the data stored in a separate buffer; such instances are represented
with an instance of the vec<T>
in the struct
.
Similarly, string
can be contained in a struct
(associated buffers are separate). In the generated C++, instances of the HIDL
handle type are represented via a pointer to the actual native handle as
instances of the underlying data type are variable-length.
Union
HIDL doesn't support anonymous unions. Otherwise, unions are similar to C.
Unions can't contain fix-up types (such as pointers, file descriptors, binder
objects). They don't need special fields or associated types and are
simply copied using memcpy()
or equivalent. An union might not directly
contain (or contain using other data structures) anything that requires setting
binder offsets (that is, handle or binder-interface references). For example:
union UnionType { uint32_t a; // vec<uint32_t> r; // Error: can't contain a vec<T> uint8_t b;1 }; fun8(UnionType info); // Legal
Unions can also be declared inside of structs. For example:
struct MyStruct { union MyUnion { uint32_t a; uint8_t b; }; // declares type but not member union MyUnion2 { uint32_t a; uint8_t b; } data; // declares type but not member }