Data types

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 the native_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 the native_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 the native_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 calls close() 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
  }