Tipos de datos

Esta sección describe los tipos de datos HIDL. Para obtener detalles de implementación, consulte HIDL C++ (para implementaciones de C++) o HIDL Java (para implementaciones de Java).

Las similitudes con C++ incluyen:

  • structs usan sintaxis C++; unions admiten la sintaxis de C++ de forma predeterminada. Ambos deben ser nombrados; No se admiten estructuras ni uniones anónimas.
  • Los typedefs están permitidos en HIDL (al igual que en C++).
  • Se permiten comentarios de estilo C++ y se copian en el archivo de encabezado generado.

Las similitudes con Java incluyen:

  • Para cada archivo, HIDL define un espacio de nombres estilo Java que debe comenzar con android.hardware. . El espacio de nombres C++ generado es ::android::hardware::… .
  • Todas las definiciones del archivo están contenidas en un contenedor interface estilo Java.
  • Las declaraciones de matrices HIDL siguen el estilo Java, no el estilo 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

Una struct o union compuesta por Standard-Layout (un subconjunto del requisito de tipos de datos antiguos) tiene un diseño de memoria consistente en el código C++ generado, aplicado con atributos de alineación explícitos en los miembros struct y union .

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

Como Java no admite tipos sin firmar, los tipos HIDL sin firmar se asignan al tipo Java firmado correspondiente. Las estructuras se asignan a clases de Java; las matrices se asignan a matrices Java; Actualmente, las uniones no son compatibles con Java. Las cadenas se almacenan internamente como UTF8. Dado que Java solo admite cadenas UTF16, los valores de cadena enviados hacia o desde una implementación de Java se traducen y es posible que no sean idénticos al volver a traducirlos, ya que los conjuntos de caracteres no siempre se asignan correctamente.

Los datos recibidos a través de IPC en C++ están marcados const y están en una memoria de solo lectura que persiste solo mientras dura la llamada a la función. Los datos recibidos a través de IPC en Java ya se han copiado en objetos Java, por lo que pueden conservarse sin necesidad de copias adicionales (y pueden modificarse).

Anotaciones

Se pueden agregar anotaciones de estilo Java a las declaraciones de tipo. Las anotaciones son analizadas por el backend de Vendor Test Suite (VTS) del compilador HIDL, pero el compilador HIDL no comprende ninguna de dichas anotaciones analizadas. En cambio, las anotaciones VTS analizadas son manejadas por el compilador VTS (VTSC).

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

Declaraciones anticipadas

En HIDL, las estructuras no pueden declararse hacia adelante, lo que imposibilita los tipos de datos autorreferenciales definidos por el usuario (por ejemplo, no se puede describir una lista enlazada o un árbol en HIDL). La mayoría de los HAL existentes (anteriores a Android 8.x) tienen un uso limitado de declaraciones directas, que se pueden eliminar reorganizando las declaraciones de estructura de datos.

Esta restricción permite que las estructuras de datos se copien por valor con una copia profunda simple, en lugar de realizar un seguimiento de los valores de puntero que pueden ocurrir varias veces en una estructura de datos autorreferencial. Si los mismos datos se pasan dos veces, como con dos parámetros de método o vec<T> que apuntan a los mismos datos, se realizan 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 incrustar en vec<T> y solo en un nivel de profundidad (no vec<vec<IFoo>> ).

Sintaxis de puntero sin formato

El lenguaje HIDL no utiliza * y no admite la flexibilidad total de los punteros sin formato C/C++. Para obtener detalles sobre cómo HIDL encapsula punteros y matrices/vectores, consulte 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 utilizar como un tipo especial en campos de estructura/unión, parámetros de método y devoluciones. Se ve como una interfaz general y 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 con 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 vec<IMyInterface> . No pueden ser miembros de vecs, estructuras, matrices o uniones anidadas.

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, consulte HIDL C++ (las FMQ no son compatibles con Java).

tipo de memoria

El tipo memory se utiliza para representar la memoria compartida no asignada en HIDL. Sólo es compatible con C++. Se puede usar un valor de este tipo en el extremo receptor para inicializar un objeto IMemory , mapear la memoria y hacerla utilizable. Para obtener más información, consulte HIDL C++ .

Advertencia: Los datos estructurados colocados en la memoria compartida DEBEN ser de un tipo cuyo formato nunca cambiará durante la vida útil de la versión de la interfaz que pasa por la memory . De lo contrario, los HAL pueden sufrir problemas fatales de compatibilidad.

tipo de puntero

El tipo pointer es solo para uso interno 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 bit a bit 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 maneja el tipo Flags igual que uint8_t .

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

manejar tipo primitivo

ADVERTENCIA: Las direcciones de cualquier tipo (incluso direcciones de dispositivos físicos) nunca deben formar parte de un identificador nativo. Pasar esta información entre procesos es peligroso y los hace susceptibles a ataques. Cualquier valor pasado entre procesos debe validarse antes de usarse para buscar la memoria asignada dentro de un proceso. De lo contrario, los malos manejos pueden causar un mal acceso a la memoria o corrupción de la misma.

La semántica HIDL es copia por valor, lo que implica que los parámetros se copian. Cualquier gran cantidad de datos, o datos que deban compartirse entre procesos (como una barrera de sincronización), se manejan pasando descriptores de archivos que apuntan a objetos persistentes: ashmem para memoria compartida, archivos reales o cualquier otra cosa que pueda esconderse detrás. un descriptor de archivo. El controlador de carpeta duplica el descriptor de archivo en el otro proceso.

mango_nativo_t

Android admite native_handle_t , un concepto de identificador 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 entradas y descriptores de archivos que se transmite por valor. Se puede almacenar un descriptor de archivo único en un identificador nativo sin entradas y con un descriptor de archivo único. Pasar identificadores utilizando identificadores nativos encapsulados con el tipo primitivo handle garantiza que los identificadores nativos se incluyan directamente en HIDL.

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

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

Manejar y archivar la propiedad del descriptor

Cuando llama a un método de interfaz HIDL que pasa (o devuelve) un objeto hidl_handle (ya sea de nivel superior o parte de un tipo compuesto), la propiedad de los descriptores de archivo contenidos en él es la siguiente:

  • La persona que llama que pasa un objeto hidl_handle como argumento conserva la propiedad de los descriptores de archivo contenidos en el valor native_handle_t que envuelve; la persona que llama debe cerrar estos descriptores de archivos cuando haya terminado con ellos.
  • El proceso que devuelve un objeto hidl_handle (pasándolo a una función _cb ) conserva la propiedad de los descriptores de archivo contenidos en el native_handle_t envuelto por el objeto; el proceso debe cerrar estos descriptores de archivos cuando termine con ellos.
  • Un transporte que recibe un hidl_handle tiene propiedad de los descriptores de archivo dentro del native_handle_t envuelto por 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 close() los descriptores del archivo cuando finalice la transacción.

HIDL no admite identificadores en Java (ya que Java no admite ningún identificador).

matrices de tamaño

Para matrices de tamaño en estructuras HIDL, sus elementos pueden ser de cualquier tipo que pueda contener una estructura:

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

Instrumentos de cuerda

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

Nota: Pasar una cadena hacia o desde Java a través de una interfaz HIDL (incluido Java a Java) provocará conversiones de juegos de caracteres que pueden no conservar exactamente 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 (por ejemplo, uint32_t)
  • Instrumentos de cuerda
  • 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)
  • Manejas
  • campo de bits<U>
  • vec<U>, donde U está en esta lista excepto la interfaz (por ejemplo, vec<vec<IFoo>> no es compatible)
  • U[] (matriz de tamaño de U), donde U está en esta lista excepto la interfaz

Tipos definidos por el usuario

Esta sección describe los tipos definidos por el usuario.

enumeración

HIDL no admite enumeraciones anónimas. De lo contrario, las enumeraciones 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 enteros en HIDL. Si no se especifica ningún valor para el primer enumerador de una enumeración basada en un tipo entero, el valor predeterminado es 0. Si no se especifica ningún 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 previamente definida. Si no se especifica ningún valor para el primer enumerador de una enumeración secundaria (en este caso FullSpectrumColor ), el valor predeterminado es el valor del último enumerador de la enumeración principal más uno. Por ejemplo:

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

Advertencia: la herencia de enumeración funciona al revés que la mayoría de los otros tipos de herencia. Un valor de enumeración secundario no se puede utilizar como valor de enumeración principal. Esto se debe a que una enumeración secundaria incluye más valores que la enumeración principal. Sin embargo, un valor de enumeración principal se puede utilizar de forma segura como valor de enumeración secundario porque los valores de enumeración secundarios son, por definición, un superconjunto de valores de enumeración principales. Tenga esto en cuenta al diseñar interfaces, ya que esto significa que los tipos que hacen referencia a enumeraciones principales no pueden hacer referencia a enumeraciones secundarias en iteraciones posteriores de su interfaz.

Los valores de enumeraciones se denominan con la sintaxis de dos puntos (no con la sintaxis de puntos 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 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 enumeraciones tienen un atributo len que se puede usar en expresiones constantes. MyEnum::len es el número total de entradas en esa enumeración. Esto es diferente del número total de valores, que puede ser menor cuando los valores se duplican.

estructura

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

HIDL no admite estructuras de datos de longitud variable contenidas completamente dentro de una estructura. Esto incluye la matriz de longitud indefinida que a veces se usa como último campo de una estructura en C/C++ (a veces se ve con un tamaño de [0] ). HIDL vec<T> representa matrices de tamaño dinámico con los datos almacenados en un búfer separado; tales instancias se representan con una instancia de vec<T> en la struct .

De manera similar, string puede estar contenida en una struct (los buffers asociados están separados). En el C++ generado, las instancias del tipo de identificador HIDL se representan mediante un puntero al identificador nativo real, ya que las instancias del tipo de datos subyacente son de longitud variable.

Unión

HIDL no apoya uniones anónimas. Por lo demás, las uniones son similares a C.

Las uniones no pueden contener tipos de reparación (punteros, descriptores de archivos, objetos de carpeta, etc.). No necesitan campos especiales ni tipos asociados y simplemente se copian mediante memcpy() o equivalente. Una unión no puede contener directamente (o contener a través de otras estructuras de datos) nada que requiera establecer compensaciones de carpeta (es decir, referencias de identificador o interfaz de carpeta). 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 estructuras. 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
  }