Tipos de datos

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

Las similitudes con C++ incluyen:

  • las structs utilizan la sintaxis de C++; las unions admiten la sintaxis de C++ de forma predeterminada. Ambos deben ser nombrados; no se admiten estructuras y uniones anónimas.
  • Typedefs están permitidos en HIDL (como lo están en C++).
  • Los comentarios de estilo C++ están permitidos y se copian en el archivo de encabezado generado.

Las similitudes con Java incluyen:

  • 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 están contenidas dentro de un contenedor de interface de estilo Java.
  • Las declaraciones de matriz 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 simples y antiguos) 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 de struct y union .

Los tipos 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 firmar, los tipos HIDL sin firmar se asignan al tipo de Java firmado correspondiente. Las estructuras se asignan a las clases de Java; las matrices se asignan a matrices de Java; Las uniones no se admiten actualmente en 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 pueden no ser idénticos en la nueva traducción, ya que los conjuntos de caracteres no siempre se asignan sin problemas.

Los datos recibidos a través de IPC en C++ se marcan como const y están en la memoria de solo lectura que persiste solo durante la duración de 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 se pueden conservar sin realizar copias adicionales (y se pueden modificar).

Anotaciones

Se pueden agregar anotaciones de estilo Java a las declaraciones de tipos. Las anotaciones son analizadas por el backend de Vendor Test Suite (VTS) del compilador HIDL, pero el compilador HIDL no entiende realmente ninguna de esas anotaciones analizadas. En su lugar, las anotaciones VTS analizadas son manejadas por VTS Compiler (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 del mismo nombre al mismo elemento.

Reenviar declaraciones

En HIDL, es posible que las estructuras no se declaren hacia adelante, lo que hace que los tipos de datos autorreferenciales y definidos por el usuario sean imposibles (por ejemplo, no puede describir una lista vinculada o un árbol en HIDL). La mayoría de las 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> s que apuntan a los mismos datos, se hacen 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 un nivel de profundidad (no vec<vec<IFoo>> ).

Sintaxis de puntero sin formato

El lenguaje HIDL no usa * y no es compatible con la flexibilidad total de los punteros sin procesar de 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 usar como un tipo especial en campos de estructura/unión, parámetros de método y devoluciones. 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 su nombre. También es idéntico a 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 un vec<IMyInterface> . No pueden ser miembros de vecs anidados, estructuras, arreglos o uniones.

MQDescriptorSync y MQDescriptorUnsync

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

tipo de memoria

El tipo de memory se utiliza para representar la memoria compartida no asignada en HIDL. Solo se admite en C++. Un valor de este tipo se puede usar en el extremo receptor para inicializar un objeto IMemory , mapeando la memoria y haciéndola 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, las HAL pueden sufrir problemas fatales de compatibilidad.

tipo de puntero

El tipo de 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 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 usar (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 a setFlags con 16 no es válido). Sin bitfield , esta información se transmite solo a través de la documentación. Además, VTS puede verificar si el valor de las banderas es un OR bit a bit de Flag.

manejar tipo primitivo

ADVERTENCIA: Las direcciones de cualquier tipo (incluso las 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. Todos los valores pasados ​​entre procesos deben validarse antes de que se utilicen para buscar la memoria asignada dentro de un proceso. De lo contrario, los identificadores incorrectos pueden provocar un acceso incorrecto a la memoria o daños en la memoria.

La semántica HIDL es copia por valor, lo que implica que se copian los parámetros. Cualquier dato grande, 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.

native_handle_t

Android es compatible con 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 enteros y descriptores de archivo que se transmite por valor. Un solo descriptor de archivo se puede almacenar en un identificador nativo sin enteros y un solo descriptor de archivo. Pasar identificadores utilizando identificadores nativos encapsulados con el tipo primitivo de 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 handle genera un puntero a un native_handle_t asignado por separado.

En versiones anteriores de Android, los identificadores 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 autogenerado de HIDL serializa y deserializa estas funciones automáticamente, sin la participación del código escrito por el usuario.

Manejar y propiedad del descriptor de archivo

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 que contiene es la siguiente:

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

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

Matrices clasificadas

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

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

Instrumentos de cuerda

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, 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 conjuntos 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 de 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, no se admite vec<vec<IFoo>> )
  • U[] (matriz de tamaño de U), donde U está en esta lista excepto en 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 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 definida previamente. 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 ú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 usar 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 usar de forma segura como un 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 cuando diseñe interfaces, ya que esto significa que los tipos que se refieren a enumeraciones principales no pueden referirse a enumeraciones secundarias en iteraciones posteriores de su interfaz.

Se hace referencia a los valores de las enumeraciones con la sintaxis de dos puntos (no 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 se duplican los valores.

estructura

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 completamente dentro de una estructura. Esto incluye la matriz 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 matrices de tamaño dinámico con los datos almacenados en un búfer separado; dichas instancias se representan con una instancia de vec<T> en la struct .

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

Unión

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

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