Tipi di dati

Le dichiarazioni di dati HIDL generano strutture di dati con layout standard C++. Queste strutture possono essere posizionate in qualsiasi punto (nello stack, a livello di file o di ambito globale o nell'heap) e possono essere composte nello stesso modo. Il codice del 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 il codice scritto dagli sviluppatori è obbligatorio per eseguire la serializzazione o la deserializzazione esplicita delle strutture di dati.

La tabella seguente mappa 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 enum in HIDL diventa un enum in C++. Ad 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 eseguire un'iterazione su un enum utilizzando ::android::hardware::hidl_enum_range. Questo intervallo include ogni enumeratore nell'ordine in cui appare nel codice sorgente HIDL, dall'enum principale all'ultimo secondario. Ad esempio, questo codice esegue un'iterazione su WRITE, READ, NONE e COMPARE in quest'ordine. Dato 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 gli iteratori inversi e può essere utilizzato in contesti constexpr. Se un valore compare in un'enumerazione più volte, viene visualizzato nell'intervallo più volte.

bitfield<T>

bitfield<T> (dove T è un enum definito dall'utente) diventa il tipo sottostante di quell'enum 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 con dimensioni fisse paragonabile è hidl_array. Un hidl_vec<T> può anche essere inizializzato in modo da 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 di utilità per tradurre a/da std::vector e puntatori T bare. Se vec<T> viene utilizzato come parametro, la funzione che lo utilizza è sovraccaricata (vengono generati due prototipi) per accettare e passare sia la struttura HIDL sia un tipo std::vector<T> per quel parametro.

array

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-dimensionale T[S1][S2]…[SN].

stringa

La classe hidl_string (parte di libhidlbase) può essere utilizzata per trasmettere stringhe tramite le interfacce HIDL ed è definita in /system/libhidl/base/include/hidl/HidlSupport.h. La prima posizione di archiviazione nel corso è un puntatore al relativo buffer di caratteri.

hidl_string sa come convertire da e verso std::string and char* (stringa in stile C) utilizzando operator=, i tipi di conversione impliciti e la funzione .c_str(). Le strutture di stringhe HIDL hanno i costruttori di copia e gli operatori di assegnazione appropriati per:

  • Carica la stringa HIDL da una stringa std::string o C.
  • Crea un nuovo std::string da una stringa HIDL.

Inoltre, le stringhe HIDL hanno costruttori di conversione, pertanto le stringhe C (char *) e C++ (std::string) possono essere utilizzate nei metodi che accettano una stringa HIDL.

struct

Un struct in HIDL può contenere solo tipi di dati di dimensioni fisse e nessuna funzione. Le definizioni di struct HIDL mappano direttamente a struct con layout standard in C++, garantendo che i struct abbiano un layout della memoria coerente. Una struct può includere tipi HIDL, tra cui handle, string e vec<T>, che fanno riferimento a buffer separati di lunghezza variabile.

handle

ATTENZIONE: gli indirizzi di qualsiasi tipo (anche quelli fisici dei dispositivi) non devono mai far parte di un handle nativo. Il passaggio di queste informazioni tra i processi è pericoloso e li rende suscettibili di attacchi. Tutti i valori trasmessi tra i processi devono essere convalidati prima di essere utilizzati per cercare la memoria allocata all'interno di un processo. In caso contrario, gli handle errati possono causare accesso alla memoria o 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 (è 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 acquisisce la proprietà del cursore native_handle_t in cui è racchiuso. Esiste solo per memorizzare in modo sicuro un puntatore a un native_handle_t in modo che possa essere utilizzato sia nei processi a 32 che a 64 bit.

Gli scenari in cui hidl_handle possiede i descrittori dei file inclusi 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 mediante la costruzione tramite copia 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 implicite ed esplicite a/da oggetti native_handle_t* . L'utilizzo principale del tipo handle in HIDL è trasmettere descrittori file tramite le interfacce HIDL. Un singolo descrittore file è quindi rappresentato da un native_handle_t senza int e da un singolo fd. Se il client e il server si trovano in un processo diverso, l'implementazione RPC si occupa automaticamente del descrittore file per garantire che entrambi i processi possano operare sullo stesso file.

Sebbene un descrittore di file ricevuto in un hidl_handle da un procedura sia valido in quella procedura, non persiste oltre la funzione di ricezione (viene chiuso quando la funzione restituisce). Un processo che vuole mantenere l'accesso permanente al descrittore file deve dup() i descrittori file racchiusi o copiare l'intero oggetto hidl_handle.

ricordo

Il tipo HIDL memory viene mappato alla classe hidl_memory in libhidlbase, che rappresenta la memoria condivisa non mappata. Si tratta dell'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 la memoria condivisa.
  2. IAllocator::allocate() restituisce un oggetto hidl_memory che può essere passato tramite l'RPC HIDL ed essere mappato in un processo utilizzando la funzione mapMemory di libhidlmemory.
  3. mapMemory restituisce un riferimento a un sp<IMemory> oggetto 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 apportate tramite un oggetto IMemory, sul lato che ha creato mem o su quello che lo 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 interface può essere utilizzata come sintassi per il tipo android.hidl.base@1.0::IBase. Inoltre, l'interfaccia corrente e le eventuali interfacce importate sono definite come tipo.

Le variabili che contengono interfacce devono essere puntatori forti: sp<IName>. Le funzioni HIDL che accettano parametri di interfaccia convertono i puntatori non elaborati in puntatori sicuri, provocando un comportamento non intuitivo (il puntatore può essere cancellato in modo imprevisto). Per evitare problemi, memorizza sempre le interfacce HIDL come sp<>.