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 parezca 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 primitivas 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 secciones siguientes 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 HIDL, desde la enumeración principal hasta el último hijo. 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 puede usarse 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 puede usarse para pasar un vector de cualquier tipo HIDL con un tamaño arbitrario. El contenedor de tamaño fijo comparable es hidl_array . Un hidl_vec<T> también se puede inicializar para que apunte a un búfer de datos externo de tipo T , usando la función hidl_vec::setToExternal() .

Además de emitir/insertar la estructura apropiadamente en el encabezado C++ generado, el uso de vec<T> genera algunas funciones convenientes para traducir hacia/desde std::vector y punteros T desnudos. Si vec<T> se usa 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

Las matrices constantes en hidl están representadas 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] .

cadena

La clase hidl_string (parte de libhidlbase ) se puede utilizar para pasar cadenas a través de interfaces HIDL y está definida en /system/libhidl/base/include/hidl/HidlSupport.h . La primera ubicación de almacenamiento de 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 cadenas HIDL tienen los constructores de copia y operadores de asignación adecuados para:

  • Cargue la cadena HIDL desde una 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 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 estructuras HIDL se asignan directamente a struct de diseño estándar en C++, lo que garantiza que struct tengan un diseño de memoria consistente. Una estructura puede incluir tipos HIDL, incluidos handle , string y vec<T> , que apuntan a búferes de longitud variable separados.

manejar

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.

El tipo 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 toma posesión del puntero native_handle_t que envuelve. Simplemente existe para almacenar de forma segura un puntero a un 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 archivos adjuntos incluyen:

  • Después de una llamada al método 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 copia y asigna desde otro objeto hidl_handle

hidl_handle proporciona conversiones implícitas y explícitas hacia/desde objetos native_handle_t* . El uso principal del tipo handle en HIDL es pasar descriptores de archivos a través de interfaces HIDL. Por lo tanto, un único descriptor de archivo está representado por un native_handle_t sin int sy 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 del archivo para garantizar que ambos procesos puedan operar en el mismo archivo.

Aunque un descriptor de archivo recibido en 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 quiera retener el acceso persistente al descriptor de archivo debe dup() los descriptores de archivo adjuntos o copiar todo el objeto hidl_handle .

memoria

El tipo 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 utilizar la memoria compartida:

  1. Obtenga una instancia de IAllocator (actualmente sólo está disponible la instancia "ashmem") y úsela 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 mapMemory de libhidlmemory .
  3. mapMemory devuelve una referencia a un objeto sp<IMemory> que se puede utilizar para acceder a la memoria. ( IMemory e IAllocator se definen en android.hidl.memory@1.0 ).

Se puede utilizar 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 se deben realizar a través de un objeto IMemory , ya sea en el lado que creó mem o en el lado que la recibe a través de HIDL 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 utilizar como azúcar sintáctico para el tipo android.hidl.base@1.0::IBase ; Además, la interfaz actual y cualquier interfaz importada se definirán como un tipo.

Las variables que contienen interfaces deben ser indicadores sólidos: sp<IName> . Las funciones HIDL que toman parámetros de interfaz convertirán punteros sin formato en punteros potentes, lo que provocará un comportamiento no intuitivo (el puntero se puede borrar inesperadamente). Para evitar problemas, almacene siempre las interfaces HIDL como sp<> .