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ámetroshouldOwn
configurado comotrue
- Cuando el objeto
hidl_handle
se crea mediante la construcción de copia de otro objetohidl_handle
- Cuando el objeto
hidl_handle
se asigna de forma copiada desde otro objetohidl_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:
- Obtén una instancia de
IAllocator
(actualmente, solo está disponible la instancia “ashmem”) y úsala para asignar memoria compartida. IAllocator::allocate()
muestra un objetohidl_memory
que se puede pasar a través de la RPC de HIDL y asignar a un proceso con la funciónmapMemory
delibhidlmemory
.mapMemory
muestra una referencia a un objetosp<IMemory>
que se puede usar para acceder a la memoria. (IMemory
yIAllocator
se definen enandroid.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<>
.