Tipi di dati

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 parametro shouldOwn impostato su true
  • Quando l'oggetto hidl_handle viene creato copiando la costruzione da un altro oggetto hidl_handle
  • Quando l'oggetto hidl_handle viene assegnato tramite copia da un altro oggetto hidl_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:

  1. Ottieni un'istanza di IAllocator (attualmente è disponibile solo l'istanza "ashmem") e utilizzala per allocare memoria condivisa.
  2. IAllocator::allocate() restituisce un oggetto hidl_memory che può essere passato attraverso HIDL RPC ed essere mappato in un processo utilizzando la funzione mapMemory di libhidlmemory .
  3. mapMemory restituisce un riferimento a un oggetto sp<IMemory> che può essere utilizzato per accedere alla memoria. ( IMemory e IAllocator sono definiti in android.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<> .