Tipos de datos

En esta sección, se describen los tipos de datos de HIDL. Para obtener detalles sobre la implementación, consulta HIDL C++ (para implementaciones de C++) o HIDL Java (para implementaciones de Java).

Las similitudes con C++ incluyen las siguientes:

  • structs usa la sintaxis de C++, y unions admite la sintaxis de C++ de forma predeterminada. Ambos deben tener un nombre; no se admiten uniones ni estructuras anónimas.
  • Los typedefs se permiten en HIDL (como en C++).
  • Se permiten los comentarios de estilo C++ y se copian en el archivo de encabezado generado.

Entre las similitudes con Java, se incluyen las siguientes:

  • Para cada archivo, HIDL define un espacio de nombres de estilo Java que debe comenzar con android.hardware.. El espacio de nombres de C++ generado es ::android::hardware::….
  • Todas las definiciones del archivo se contienen en un wrapper interface de estilo Java.
  • Las declaraciones de arrays de HIDL siguen el estilo de Java, no el de C++. Ejemplo:
    struct Point {
        int32_t x;
        int32_t y;
    };
    Point[3] triangle;   // sized array
  • Los comentarios son similares al formato javadoc.

Representación de datos

Un struct o union compuesto de Standard-Layout (un subconjunto del requisito de tipos de datos simples) tiene un diseño de memoria coherente en el código C++ generado, que se aplica con atributos de alineación explícitos en los miembros struct y union.

Los tipos de HIDL primitivos, así como los tipos enum y bitfield (que siempre se derivan de tipos primitivos), se asignan a tipos estándar de C++, como std::uint32_t de cstdint.

Como Java no admite tipos sin firma, los tipos de HIDL sin firma se asignan al tipo de Java firmado correspondiente. Las estructuras se asignan a clases de Java, los arrays se asignan a arrays de Java y las uniones no son compatibles con Java en este momento. Las cadenas se almacenan internamente como UTF8. Dado que Java solo admite cadenas UTF16, los valores de cadena que se envían a una implementación de Java o desde ella se traducen y pueden no ser idénticos en la nueva traducción, ya que los conjuntos de caracteres no siempre se asignan de forma fluida.

Los datos recibidos a través de IPC en C++ se marcan como const y se encuentran en la memoria de solo lectura que persiste solo durante el tiempo que dura la llamada a la función. Los datos que se reciben a través de IPC en Java ya se copiaron en objetos Java, por lo que se pueden retener sin copias adicionales (y se pueden modificar).

Anotaciones

Se pueden agregar anotaciones de estilo Java a las declaraciones de tipo. El backend del conjunto de pruebas de proveedores (VTS) del compilador de HIDL analiza las anotaciones, pero el compilador de HIDL no comprende ninguna de ellas. En su lugar, el compilador de VTS (VTSC) controla las anotaciones de VTS analizadas.

Las anotaciones usan la sintaxis de Java: @annotation, @annotation(value) o @annotation(id=value, id=value…), en las que el valor puede ser una expresión constante, una cadena o una lista de valores dentro de {}, al igual que en Java. Se pueden adjuntar varias anotaciones con el mismo nombre al mismo elemento.

Declaraciones de reenvío

En HIDL, es posible que las estructuras no se declaren de forma anticipada, lo que hace imposible los tipos de datos autoreferenciales definidos por el usuario (por ejemplo, no puedes describir una lista vinculada ni un árbol en HIDL). La mayoría de los HAL existentes (anteriores a Android 8.x) tienen un uso limitado de las declaraciones de reenvío, que se pueden quitar reorganizando las declaraciones de la estructura de datos.

Esta restricción permite que las estructuras de datos se copien por valor con una copia profunda simple, en lugar de hacer un seguimiento de los valores de puntero que podrían ocurrir varias veces en una estructura de datos autorreferencial. Si se pasan los mismos datos dos veces, como con dos parámetros de método o vec<T> que apuntan a los mismos datos, se crean y entregan dos copias separadas.

Declaraciones anidadas

HIDL admite declaraciones anidadas en tantos niveles como se desee (con una excepción que se indica a continuación). Por ejemplo:

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
    }
    

La excepción es que los tipos de interfaz solo se pueden incorporar en vec<T> y solo a un nivel de profundidad (no vec<vec<IFoo>>).

Sintaxis de puntero sin procesar

El lenguaje HIDL no usa * y no admite la flexibilidad completa de los punteros sin procesar de C/C++. Para obtener detalles sobre cómo HIDL encapsula los punteros y los arrays o vectores, consulta la plantilla vec<T>.

Interfaces

La palabra clave interface tiene dos usos.

  • Abre la definición de una interfaz en un archivo .hal.
  • Se puede usar como un tipo especial en campos de unión o struct, parámetros de métodos y valores que se devuelven. Se considera una interfaz general y un sinónimo de android.hidl.base@1.0::IBase.

Por ejemplo, IServiceManager tiene el siguiente método:

get(string fqName, string name) generates (interface service);

El método promete buscar alguna interfaz por nombre. También es idéntico reemplazar la interfaz por android.hidl.base@1.0::IBase.

Las interfaces solo se pueden pasar de dos maneras: como parámetros de nivel superior o como miembros de un vec<IMyInterface>. No pueden ser miembros de vetecs, estructuras, arrays ni uniones anidados.

MQDescriptorSync y MQDescriptorUnsync

Los tipos MQDescriptorSync y MQDescriptorUnsync pasan descriptores de cola de mensajes rápidos (FMQ) sincronizados o no sincronizados a través de una interfaz HIDL. Para obtener más información, consulta HIDL C++ (los FMQ no son compatibles con Java).

tipo de memoria

El tipo memory se usa para representar la memoria compartida no asignada en DIDL. Solo es compatible con C++. Se puede usar un valor de este tipo en el extremo receptor para inicializar un objeto IMemory, asignar la memoria y hacer que sea utilizable. Para obtener más información, consulta HIDL C++.

Advertencia: Los datos estructurados que se colocan en la memoria compartida DEBEN ser de un tipo cuyo formato nunca cambie durante el ciclo de vida de la versión de la interfaz que pasa el memory. De lo contrario, los HAL pueden sufrir problemas de compatibilidad fatales.

tipo de puntero

El tipo pointer es solo para uso interno de HIDL.

Plantilla de tipo bitfield<T>

bitfield<T> en el que T es una enumeración definida por el usuario sugiere que el valor es un OR binario de los valores de enumeración definidos en T. En el código generado, bitfield<T> aparece como el tipo subyacente de T. Por ejemplo:

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);

El compilador controla el tipo Flags de la misma manera que uint8_t.

¿Por qué no usar (u)int8_t/(u)int16_t/(u)int32_t/(u)int64_t? El uso de bitfield proporciona información adicional de HAL al lector, que ahora sabe que setFlags toma un valor de OR de bits de la marca (es decir, sabe que llamar a setFlags con 16 no es válido). Sin bitfield, esta información solo se transmite a través de la documentación. Además, VTS puede verificar si el valor de las marcas es un OR binario de la marca.

Controles de tipo primitivo

ADVERTENCIA: Las direcciones de cualquier tipo (incluso las direcciones de dispositivos físicos) nunca deben ser parte de un identificador nativo. Pasar esta información entre procesos es peligroso y los hace susceptibles a ataques. Cualquier valor que se pase entre procesos debe validarse antes de que se use para buscar la memoria asignada dentro de un proceso. De lo contrario, los controladores incorrectos podrían provocar un acceso incorrecto a la memoria o corrupción de la memoria.

La semántica de HIDL es de copia por valor, lo que implica que se copian los parámetros. Cualquier dato grande o que se deba compartir entre procesos (como una cerca de sincronización) se controla pasando descriptores de archivos que apuntan a objetos persistentes: ashmem para la memoria compartida, los archivos reales o cualquier otra cosa que se pueda ocultar detrás de un descriptor de archivo. El controlador de Binder duplica el descriptor de archivos en el otro proceso.

native_handle_t

Android admite native_handle_t, un concepto de control general definido en 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;

Un identificador nativo es una colección de enteros y descriptores de archivos que se pasa por valor. Un solo descriptor de archivo se puede almacenar en un identificador nativo sin ints y un solo descriptor de archivo. Pasar controladores con controladores nativos encapsulados con el tipo primitivo handle garantiza que los controladores nativos se incluyan directamente en HIDL.

Como un native_handle_t tiene un tamaño variable, no se puede incluir directamente en una struct. Un campo de control genera un puntero a un native_handle_t asignado por separado.

En versiones anteriores de Android, los controladores nativos se creaban con las mismas funciones presentes en libcutils. En Android 8.0 y versiones posteriores, estas funciones ahora se copian en el espacio de nombres android::hardware::hidl o se mueven al NDK. El código generado automáticamente por HIDL serializa y deserializa estas funciones automáticamente, sin la participación del código escrito por el usuario.

Controla la propiedad del descriptor de archivo

Cuando llamas a un método de interfaz HIDL que pasa (o muestra) un objeto hidl_handle (ya sea de nivel superior o parte de un tipo compuesto), la propiedad de los descriptores de archivos que contiene es la siguiente:

  • El llamador que pasa un objeto hidl_handle como argumento retiene la propiedad de los descriptores de archivos contenidos en el native_handle_t que une. El llamador debe cerrar estos descriptores de archivos cuando termine con ellos.
  • El proceso que muestra un objeto hidl_handle (pasándolo a una función _cb) retiene la propiedad de los descriptores de archivos contenidos en el native_handle_t unido por el objeto. El proceso debe cerrar estos descriptores de archivos cuando termine con ellos.
  • Un transporte que recibe un hidl_handle tiene la propiedad de los descriptores de archivos dentro del native_handle_t que une el objeto. El receptor puede usar estos descriptores de archivos tal como están durante la devolución de llamada de la transacción, pero debe clonar el identificador nativo para usar los descriptores de archivos más allá de la devolución de llamada. El transporte llama automáticamente a close() para los descriptores de archivos cuando se completa la transacción.

HIDL no admite controladores en Java (ya que Java no admite controladores en absoluto).

Arrays de tamaño

En el caso de los arrays de tamaño en las estructuras de HIDL, sus elementos pueden ser de cualquier tipo que pueda contener una estructura:

struct foo {
uint32_t[3] x; // array is contained in foo
};

Strings

Las cadenas aparecen de manera diferente en C++ y Java, pero el tipo de almacenamiento de transporte subyacente es una estructura de C++. Para obtener más información, consulta Tipos de datos de HIDL C++ o Tipos de datos de HIDL Java.

Nota: Pasar una cadena a Java o desde Java a través de una interfaz HIDL (incluida la conversión de Java a Java) genera conversiones de conjuntos de caracteres que podrían no conservar la codificación original.

Plantilla de tipo vec<T>

La plantilla vec<T> representa un búfer de tamaño variable que contiene instancias de T.

T puede ser uno de los siguientes:

  • Tipos primitivos (p.ej., uint32_t)
  • Strings
  • Enumeraciones definidas por el usuario
  • Estructuras definidas por el usuario
  • Interfaces o la palabra clave interface (vec<IFoo>, vec<interface> solo se admite como parámetro de nivel superior)
  • Identificadores
  • bitfield<U>
  • vec<U>, donde U está en esta lista, excepto la interfaz (p.ej., no se admite vec<vec<IFoo>>)
  • U[] (array de tamaño de U), donde U está en esta lista, excepto la interfaz

Tipos definidos por el usuario

En esta sección, se describen los tipos definidos por el usuario.

Enum

HIDL no admite enums anónimas. De lo contrario, las enums en HIDL son similares a C++11:

enum name : type { enumerator , enumerator = constexpr ,   }

Una enumeración base se define en términos de uno de los tipos de números enteros en HIDL. Si no se especifica un valor para el primer enumerador de una enumeración basada en un tipo de número entero, el valor predeterminado es 0. Si no se especifica un valor para un enumerador posterior, el valor predeterminado es el valor anterior más uno. Por ejemplo:

// RED == 0
// BLUE == 4 (GREEN + 1)
enum Color : uint32_t { RED, GREEN = 3, BLUE }

Una enumeración también puede heredar de una enumeración definida anteriormente. Si no se especifica un valor para el primer enumerador de una enumeración secundaria (en este caso, FullSpectrumColor), el valor predeterminado es el del último enumerador de la enumeración superior más uno. Por ejemplo:

// ULTRAVIOLET == 5 (Color:BLUE + 1)
enum FullSpectrumColor : Color { ULTRAVIOLET }

Advertencia: La herencia de enum funciona de forma inversa a la mayoría de los otros tipos de herencia. No se puede usar un valor de enumeración secundario como valor de enumeración superior. Esto se debe a que una enumeración secundaria incluye más valores que la superior. Sin embargo, un valor de enumeración superior se puede usar de forma segura como un valor de enumeración secundario, ya que, por definición, los valores de enumeración secundarios son un superconjunto de los valores de enumeración superiores. Ten esto en cuenta cuando diseñes interfaces, ya que esto significa que los tipos que hacen referencia a enumeraciones superiores no pueden hacer referencia a enumeraciones secundarias en iteraciones posteriores de tu interfaz.

Se hace referencia a los valores de las enums con la sintaxis de dos puntos (no con la sintaxis de punto como tipos anidados). La sintaxis es Type:VALUE_NAME. No es necesario especificar el tipo si se hace referencia al valor en el mismo tipo de enumeración o en los tipos secundarios. Ejemplo:

enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 };
enum Color : Grayscale { RED = WHITE + 1 };
enum Unrelated : uint32_t { FOO = Color:RED + 1 };

A partir de Android 10, las enums tienen un atributo len que se puede usar en expresiones constantes. MyEnum::len es la cantidad total de entradas en esa enumeración. Esto es diferente de la cantidad total de valores, que puede ser menor cuando se duplican los valores.

Struct

HIDL no admite estructuras anónimas. De lo contrario, las estructuras en HIDL son muy similares a C.

HIDL no admite estructuras de datos de longitud variable contenidas por completo en una struct. Esto incluye el array de longitud indefinida que, a veces, se usa como el último campo de una estructura en C/C++ (a veces, se ve con un tamaño de [0]). HIDL vec<T> representa arrays de tamaño dinámico con los datos almacenados en un búfer independiente. Estas instancias se representan con una instancia de vec<T> en struct.

Del mismo modo, string puede estar contenido en un struct (los búferes asociados son independientes). En el código C++ generado, las instancias del tipo de controlador HIDL se representan a través de un puntero al controlador nativo real, ya que las instancias del tipo de datos subyacente son de longitud variable.

Union

HIDL no admite uniones anónimas. De lo contrario, las uniones son similares a C.

Las uniones no pueden contener tipos de corrección (como punteros, descriptores de archivos y objetos de Binder). No necesitan campos especiales ni tipos asociados, y se copian con memcpy() o un elemento equivalente. Es posible que una unión no contenga directamente (o contenga con otras estructuras de datos) nada que requiera establecer compensaciones de Binder (es decir, referencias de control o interfaz de Binder). Por ejemplo:

union UnionType {
uint32_t a;
//  vec<uint32_t> r;  // Error: can't contain a vec<T>
uint8_t b;1
};
fun8(UnionType info); // Legal

Las uniones también se pueden declarar dentro de structs. Por ejemplo:

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
  }