Tipos de datos

Las declaraciones de datos HIDL generan estructuras de datos de diseño estándar de C++. Estas estructuras se pueden colocar en cualquier lugar que se sienta natural (en la pila, en el archivo o en el ámbito global, o en el montón) y se pueden componer de la misma manera. El código del cliente llama al código proxy HIDL pasando referencias constantes y tipos primitivos, mientras que el código auxiliar y el código proxy ocultan los detalles de la serialización.

Nota: En ningún momento se requiere código escrito por el desarrollador para serializar o deserializar explícitamente estructuras de datos.

La siguiente tabla asigna primitivos HIDL a tipos de datos C++:

Tipo HIDL Tipo C++ Encabezado/Biblioteca
enum
enum class
uint8_t..uint64_t
uint8_t..uint64_t
<stdint.h>
int8_t..int64_t
int8_t..int64_t
<stdint.h>
float
float
double
double
vec<T>
hidl_vec<T>
libhidlbase
T[S1][S2]...[SN]
T[S1][S2]...[SN]
string
hidl_string
libhidlbase
handle
hidl_handle
libhidlbase
safe_union
(custom) struct
struct
struct
union
union
fmq_sync
MQDescriptorSync
libhidlbase
fmq_unsync
MQDescriptorUnsync
libhidlbase

Las siguientes secciones describen los tipos de datos con más detalle.

enumeración

Una enumeración en HIDL se convierte en una enumeración en C++. Por ejemplo:

enum Mode : uint8_t { WRITE = 1 << 0, READ = 1 << 1 };
enum SpecialMode : Mode { NONE = 0, COMPARE = 1 << 2 };

… se convierte en:

enum class Mode : uint8_t { WRITE = 1, READ = 2 };
enum class SpecialMode : uint8_t { WRITE = 1, READ = 2, NONE = 0, COMPARE = 4 };

A partir de Android 10, se puede iterar una enumeración usando ::android::hardware::hidl_enum_range . Este rango incluye cada enumerador en el orden en que aparece en el código fuente de HIDL, desde la enumeración principal hasta el último elemento secundario. Por ejemplo, este código itera sobre WRITE , READ , NONE y COMPARE en ese orden. Dado SpecialMode anterior:

template <typename T>
using hidl_enum_range = ::android::hardware::hidl_enum_range<T>

for (SpecialMode mode : hidl_enum_range<SpecialMode>) {...}

hidl_enum_range también implementa iteradores inversos y se puede usar en contextos constexpr . Si un valor aparece en una enumeración varias veces, el valor aparece en el rango varias veces.

campo de bits<T>

bitfield<T> (donde T es una enumeración definida por el usuario) se convierte en el tipo subyacente de esa enumeración en C++. En el ejemplo anterior, bitfield<Mode> se convierte en uint8_t .

vec<T>

La plantilla de clase hidl_vec<T> es parte de libhidlbase y se puede usar para pasar un vector de cualquier tipo HIDL con un tamaño arbitrario. El contenedor de tamaño fijo comparable es hidl_array . También se puede inicializar un hidl_vec<T> para que apunte a un búfer de datos externo de tipo T , utilizando la hidl_vec::setToExternal() .

Además de emitir/insertar la estructura de forma adecuada en el encabezado de C++ generado, el uso de vec<T> genera algunas funciones convenientes para traducir a/desde punteros std::vector y bare T Si se usa vec<T> como parámetro, la función que lo usa se sobrecargará (se generarán dos prototipos) para aceptar y pasar tanto la estructura HIDL como un tipo std::vector<T> para ese parámetro.

formación

Los arreglos constantes en hidl están representados por la clase hidl_array en libhidlbase . Un hidl_array<T, S1, S2, …, SN> representa una matriz de tamaño fijo N dimensional T[S1][S2]…[SN] .

cuerda

La clase hidl_string (parte de libhidlbase ) se puede usar para pasar cadenas a través de interfaces HIDL y se define en /system/libhidl/base/include/hidl/HidlSupport.h . La primera ubicación de almacenamiento en la clase es un puntero a su búfer de caracteres.

hidl_string sabe cómo convertir hacia y desde std::string and char* (cadena estilo C) usando operator= , conversiones implícitas y la función .c_str() . Las estructuras de cadena HIDL tienen los constructores de copia y los operadores de asignación apropiados para:

  • Cargue la cadena HIDL desde un std::string o una cadena C.
  • Cree una nueva std::string a partir de una cadena HIDL.

Además, las cadenas HIDL tienen constructores de conversión, por lo que las cadenas C ( char * ) y las cadenas C++ ( std::string ) se pueden usar en métodos que toman una cadena HIDL.

estructura

Una struct en HIDL solo puede contener tipos de datos de tamaño fijo y ninguna función. Las definiciones de estructura HIDL se asignan directamente a struct de diseño estándar en C++, lo que garantiza que las struct tengan un diseño de memoria coherente. Una estructura puede incluir tipos HIDL, incluidos handle , string y vec<T> , que apuntan a búferes separados de longitud variable.

resolver

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. Cualquier valor pasado entre procesos debe validarse antes de usarse 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.

El tipo de handle está representado por la estructura hidl_handle en C++, que es un contenedor simple alrededor de un puntero a un objeto const native_handle_t (esto ha estado presente en Android durante mucho tiempo).

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;

De forma predeterminada, hidl_handle no se apropia del puntero native_handle_t que envuelve. Simplemente existe para almacenar de forma segura un puntero a native_handle_t de modo que pueda usarse en procesos de 32 y 64 bits.

Los escenarios en los que hidl_handle posee sus descriptores de archivo adjuntos incluyen:

  • Después de una llamada al setTo(native_handle_t* handle, bool shouldOwn) con el parámetro shouldOwn establecido en true
  • Cuando el objeto hidl_handle se crea copiando la construcción de otro objeto hidl_handle
  • Cuando el objeto hidl_handle se asigna por copia desde otro objeto hidl_handle

hidl_handle proporciona conversiones implícitas y explícitas a/desde objetos native_handle_t* . El uso principal del tipo de handle en HIDL es pasar descriptores de archivo a través de interfaces HIDL. Por lo tanto, un descriptor de archivo único se representa mediante native_handle_t sin int s y un solo fd . Si el cliente y el servidor viven en un proceso diferente, la implementación de RPC se encargará automáticamente del descriptor de archivo para garantizar que ambos procesos puedan operar en el mismo archivo.

Aunque un descriptor de archivo recibido en un hidl_handle por un proceso será válido en ese proceso, no persistirá más allá de la función receptora (se cerrará una vez que la función regrese). Un proceso que desee conservar el acceso persistente al descriptor de archivo debe dup() los descriptores de archivo adjuntos o copiar todo el objeto hidl_handle .

memoria

El tipo de memory HIDL se asigna a la clase hidl_memory en libhidlbase , que representa la memoria compartida no asignada. Este es el objeto que se debe pasar entre procesos para compartir memoria en HIDL. Para usar la memoria compartida:

  1. Obtenga una instancia de IAllocator (actualmente solo está disponible la instancia "ashmem") y utilícela para asignar memoria compartida.
  2. IAllocator::allocate() devuelve un objeto hidl_memory que puede pasarse a través de HIDL RPC y asignarse a un proceso utilizando la función libhidlmemory de mapMemory .
  3. mapMemory devuelve una referencia a un objeto sp<IMemory> que se puede usar para acceder a la memoria. ( IMemory e IAllocator se definen en android.hidl.memory@1.0 ).

Se puede usar una instancia de IAllocator para asignar memoria:

#include <android/hidl/allocator/1.0/IAllocator.h>
#include <android/hidl/memory/1.0/IMemory.h>
#include <hidlmemory/mapping.h>
using ::android::hidl::allocator::V1_0::IAllocator;
using ::android::hidl::memory::V1_0::IMemory;
using ::android::hardware::hidl_memory;
....
  sp<IAllocator> ashmemAllocator = IAllocator::getService("ashmem");
  ashmemAllocator->allocate(2048, [&](bool success, const hidl_memory& mem) {
        if (!success) { /* error */ }
        // now you can use the hidl_memory object 'mem' or pass it around
  }));

Los cambios reales en la memoria deben realizarse a través de un objeto IMemory , ya sea en el lado que creó la memoria o en el lado que la recibe a través de mem RPC.

// Same includes as above

sp<IMemory> memory = mapMemory(mem);
void* data = memory->getPointer();
memory->update();
// update memory however you wish after calling update and before calling commit
data[0] = 42;
memory->commit();
// …
memory->update(); // the same memory can be updated multiple times
// …
memory->commit();

interfaz

Las interfaces se pueden pasar como objetos. La palabra interfaz se puede usar como azúcar sintáctica para el tipo android.hidl.base@1.0::IBase ; además, la interfaz actual y las interfaces importadas se definirán como un tipo.

Las variables que contienen interfaces deben ser punteros fuertes: sp<IName> . Las funciones HIDL que toman parámetros de interfaz convertirán los punteros sin procesar en punteros fuertes, lo que provocará un comportamiento no intuitivo (el puntero se puede borrar de forma inesperada). Para evitar problemas, almacene siempre las interfaces HIDL como sp<> .