Tipos de datos

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

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

En la siguiente tabla, se asignan primitivas de HIDL a tipos de datos de C++:

Tipo de HIDL Tipo de C++ Header/library
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

En las siguientes secciones, se describen los tipos de datos con más detalle.

enum

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 con ::android::hardware::hidl_enum_range. Este rango incluye todos los enumeradores en el orden en que aparecen en el código fuente de HIDL, desde la enumeración superior 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 aparecerá en el rango varias veces.

bitfield<T>

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

vec<T>

La plantilla de clase hidl_vec<T> forma parte de libhidlbase y se puede usar para pasar un vector de cualquier tipo de 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 con la función hidl_vec::setToExternal().

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

array

Los arrays constantes en hidl se representan con la clase hidl_array en libhidlbase. Un hidl_array<T, S1, S2, …, SN> representa un array de tamaño fijo de N dimensiones T[S1][S2]…[SN].

string

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 de std::string and char* (cadena de estilo C) y viceversa con operator=, conversiones implícitas y la función .c_str(). Las estructuras de cadenas de HIDL tienen los constructores de copia y los operadores de asignación adecuados para lo siguiente:

  • Carga la cadena HIDL desde una std::string o una cadena C.
  • Crea un std::string nuevo a partir de una cadena HIDL.

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

struct

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

identificador

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 usarlo para buscar la memoria asignada dentro de un proceso. De lo contrario, los controladores incorrectos pueden provocar un acceso incorrecto a la memoria o corrupción de la memoria.

El tipo handle está representado por la estructura hidl_handle en C++, que es un wrapper 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 une. Solo existe para almacenar de forma segura un puntero a un native_handle_t de modo que se pueda usar en procesos de 32 y 64 bits.

Entre las situaciones en las que hidl_handle es propietario de sus descriptores de archivo adjuntos, se incluyen las siguientes:

  • Después de una llamada al método setTo(native_handle_t* handle, bool shouldOwn) con el parámetro shouldOwn configurado como true
  • Cuando el objeto hidl_handle se crea mediante la construcción de copia de otro objeto hidl_handle
  • Cuando el objeto hidl_handle se asigna de forma copiada desde otro objeto hidl_handle

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

Aunque un descriptor de archivo que recibe un proceso en un hidl_handle es válido en ese proceso, no persiste más allá de la función receptora (se cierra cuando se muestra la función). Un proceso que desee retener el acceso persistente al descriptor de archivos debe dup() los descriptores de archivos adjuntos o copiar todo el objeto hidl_handle.

memoria

El tipo memory de 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, haz lo siguiente:

  1. Obtén una instancia de IAllocator (actualmente, solo está disponible la instancia “ashmem”) y úsala para asignar memoria compartida.
  2. IAllocator::allocate() muestra un objeto hidl_memory que se puede pasar a través de la RPC de HIDL y asignar a un proceso con la función mapMemory de libhidlmemory.
  3. mapMemory muestra una referencia a un objeto sp<IMemory> que se puede usar para acceder a la memoria. (IMemory y 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ó mem o en el lado que lo recibe a través de RPC de HIDL.

// 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 interface se puede usar como sintaxis enriquecida para el tipo android.hidl.base@1.0::IBase. Además, la interfaz actual y las interfaces importadas se definen como un tipo.

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