Le dichiarazioni di dati HIDL generano strutture di dati con layout standard C++. Queste strutture possono essere posizionate ovunque risulti naturale (nello stack, nell'ambito file o globale o nell'heap) e possono essere composte nello stesso modo. Il codice client chiama il codice proxy HIDL passando riferimenti const e tipi primitivi, mentre il codice stub e proxy nasconde i dettagli della serializzazione.
Nota: in nessun momento è necessario il codice scritto dallo sviluppatore per serializzare o deserializzare esplicitamente le strutture di dati.
La tabella seguente associa le primitive HIDL ai tipi di dati C++:
Tipo HIDL | Tipo C++ | Intestazione/Libreria |
---|---|---|
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 |
Le sezioni seguenti descrivono i tipi di dati in modo più dettagliato.
enum
Un'enumerazione in HIDL diventa un'enumerazione in C++. Per esempio:
enum Mode : uint8_t { WRITE = 1 << 0, READ = 1 << 1 }; enum SpecialMode : Mode { NONE = 0, COMPARE = 1 << 2 };
… diventa:
enum class Mode : uint8_t { WRITE = 1, READ = 2 }; enum class SpecialMode : uint8_t { WRITE = 1, READ = 2, NONE = 0, COMPARE = 4 };
A partire da Android 10, è possibile ripetere un'enumerazione utilizzando ::android::hardware::hidl_enum_range
. Questo intervallo include ogni enumeratore nell'ordine in cui appare nel codice sorgente HIDL, a partire dall'enumerazione principale fino all'ultimo figlio. Ad esempio, questo codice esegue l'iterazione su WRITE
, READ
, NONE
e COMPARE
in quest'ordine. Data SpecialMode
sopra:
template <typename T> using hidl_enum_range = ::android::hardware::hidl_enum_range<T> for (SpecialMode mode : hidl_enum_range<SpecialMode>) {...}
hidl_enum_range
implementa anche iteratori inversi e può essere utilizzato in contesti constexpr
. Se un valore appare più volte in un'enumerazione, il valore apparirà più volte nell'intervallo.
campo di bit<T>
bitfield<T>
(dove T
è un'enumerazione definita dall'utente) diventa il tipo sottostante di tale enumerazione in C++. Nell'esempio precedente, bitfield<Mode>
diventa uint8_t
.
vec<T>
Il modello di classe hidl_vec<T>
fa parte di libhidlbase
e può essere utilizzato per passare un vettore di qualsiasi tipo HIDL con una dimensione arbitraria. Il contenitore a dimensione fissa comparabile è hidl_array
. Un hidl_vec<T>
può anche essere inizializzato per puntare a un buffer di dati esterno di tipo T
, utilizzando la funzione hidl_vec::setToExternal()
.
Oltre a emettere/inserire la struttura in modo appropriato nell'intestazione C++ generata, l'uso di vec<T>
genera alcune funzioni utili da tradurre in/da std::vector
e puntatori T
semplici. Se vec<T>
viene utilizzato come parametro, la funzione che lo utilizza verrà sovraccaricata (verranno generati due prototipi) per accettare e passare sia la struttura HIDL che un tipo std::vector<T>
per quel parametro.
vettore
Gli array costanti in hidl sono rappresentati dalla classe hidl_array
in libhidlbase
. Un hidl_array<T, S1, S2, …, SN>
rappresenta un array di dimensioni fisse N dimensionali T[S1][S2]…[SN]
.
corda
La classe hidl_string
(parte di libhidlbase
) può essere utilizzata per passare stringhe su interfacce HIDL ed è definita in /system/libhidl/base/include/hidl/HidlSupport.h
. La prima posizione di archiviazione nella classe è un puntatore al suo buffer di caratteri.
hidl_string
sa come convertire da e verso std::string and char*
(stringa in stile C) utilizzando operator=
, cast impliciti e la funzione .c_str()
. Le strutture di stringa HIDL dispongono dei costruttori di copia e degli operatori di assegnazione appropriati per:
- Carica la stringa HIDL da una
std::string
o una stringa C. - Crea un nuovo
std::string
da una stringa HIDL.
Inoltre, le stringhe HIDL dispongono di costruttori di conversione, pertanto è possibile utilizzare stringhe C ( char *
) e stringhe C++ ( std::string
) su metodi che accettano una stringa HIDL.
struttura
Una struct
in HIDL può contenere solo tipi di dati a dimensione fissa e nessuna funzione. Le definizioni delle strutture HIDL vengono mappate direttamente alle struct
con layout standard in C++, garantendo che le struct
abbiano un layout di memoria coerente. Una struttura può includere tipi HIDL, inclusi handle
, string
e vec<T>
, che puntano a buffer separati di lunghezza variabile.
maniglia
ATTENZIONE: indirizzi di qualsiasi tipo (anche indirizzi di dispositivi fisici) non devono mai far parte di un handle nativo. Il passaggio di queste informazioni tra processi è pericoloso e li rende suscettibili agli attacchi. Tutti i valori passati tra i processi devono essere convalidati prima di essere utilizzati per cercare la memoria allocata all'interno di un processo. In caso contrario, handle errati potrebbero causare un accesso non corretto alla memoria o un danneggiamento della memoria.
Il tipo handle
è rappresentato dalla struttura hidl_handle
in C++, che è un semplice wrapper attorno a un puntatore a un oggetto const native_handle_t
(questo è presente in Android da molto tempo).
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;
Per impostazione predefinita, hidl_handle
non assume la proprietà del puntatore native_handle_t
che racchiude. Esiste semplicemente per archiviare in modo sicuro un puntatore a native_handle_t
in modo che possa essere utilizzato sia in processi a 32 che a 64 bit.
Gli scenari in cui hidl_handle
possiede i descrittori di file racchiusi includono:
- Dopo una chiamata al metodo
setTo(native_handle_t* handle, bool shouldOwn)
con il parametroshouldOwn
impostato sutrue
- Quando l'oggetto
hidl_handle
viene creato copiando la costruzione da un altro oggettohidl_handle
- Quando l'oggetto
hidl_handle
viene assegnato tramite copia da un altro oggettohidl_handle
hidl_handle
fornisce conversioni sia implicite che esplicite da/verso oggetti native_handle_t*
. L'utilizzo principale del tipo di handle
in HIDL è passare descrittori di file sulle interfacce HIDL. Un singolo descrittore di file è quindi rappresentato da un native_handle_t
senza int
e un singolo fd
. Se il client e il server risiedono in un processo diverso, l'implementazione RPC si occuperà automaticamente del descrittore di file per garantire che entrambi i processi possano operare sullo stesso file.
Sebbene un descrittore di file ricevuto in un hidl_handle
da un processo sarà valido in quel processo, non persisterà oltre la funzione ricevente (verrà chiuso una volta restituita la funzione). Un processo che desidera mantenere l'accesso persistente al descrittore di file deve dup()
i descrittori di file racchiusi o copiare l'intero oggetto hidl_handle
.
memoria
Il tipo memory
HIDL è mappato alla classe hidl_memory
in libhidlbase
, che rappresenta la memoria condivisa non mappata. Questo è l'oggetto che deve essere passato tra i processi per condividere la memoria in HIDL. Per utilizzare la memoria condivisa:
- Ottieni un'istanza di
IAllocator
(attualmente è disponibile solo l'istanza "ashmem") e utilizzala per allocare memoria condivisa. -
IAllocator::allocate()
restituisce un oggettohidl_memory
che può essere passato attraverso HIDL RPC ed essere mappato in un processo utilizzando la funzionemapMemory
dilibhidlmemory
. -
mapMemory
restituisce un riferimento a un oggettosp<IMemory>
che può essere utilizzato per accedere alla memoria. (IMemory
eIAllocator
sono definiti inandroid.hidl.memory@1.0
.)
Un'istanza di IAllocator
può essere utilizzata per allocare 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 }));
Le modifiche effettive alla memoria devono essere eseguite tramite un oggetto IMemory
, sul lato che ha creato mem
o sul lato che la riceve tramite 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();
interfaccia
Le interfacce possono essere passate come oggetti. La parola interfaccia può essere utilizzata come zucchero sintattico per il tipo android.hidl.base@1.0::IBase
; inoltre, l'interfaccia corrente e le eventuali interfacce importate verranno definite come tipo.
Le variabili che contengono le interfacce dovrebbero essere puntatori forti: sp<IName>
. Le funzioni HIDL che accettano parametri di interfaccia convertiranno i puntatori grezzi in puntatori forti, causando un comportamento non intuitivo (il puntatore può essere cancellato inaspettatamente). Per evitare problemi, archivia sempre le interfacce HIDL come sp<>
.