Questa sezione descrive i tipi di dati HIDL. Per i dettagli sull'implementazione, consulta HIDL C++ (per le implementazioni C++) o HIDL Java (per le implementazioni Java).
Le somiglianze con C++ includono:
structs
utilizza la sintassi C++.unions
supporta la sintassi C++ per impostazione predefinita. Entrambi devono essere denominati; le strutture e le unioni anonime non sono supportate.- I typedef sono consentiti in HIDL (come in C++).
- I commenti in stile C++ sono consentiti e vengono copiati nel file di intestazione generato.
Le somiglianze con Java includono:
- Per ogni file, HIDL definisce uno spazio dei nomi in stile Java che deve iniziare con
android.hardware.
. Lo spazio dei nomi C++ generato è::android::hardware::…
. - Tutte le definizioni del file sono contenute in un wrapper
interface
in stile Java. - Le dichiarazioni di array HIDL seguono lo stile Java, non lo stile C++. Esempio:
struct Point { int32_t x; int32_t y; }; Point[3] triangle; // sized array
- I commenti sono simili al formato javadoc.
Rappresentazione dei dati
Un struct
o un union
composto da
layout standard
(un sottoinsieme del requisito dei tipi di dati semplici) ha un
layout della memoria coerente nel codice C++ generato, applicato con attributi di allineamento esplicito su
gli elementi struct
e union
.
I tipi HIDL primitivi, nonché i tipi enum
e bitfield
(che derivano sempre da tipi primitivi), vengono mappati ai tipi C++ standard come std::uint32_t
di cstdint.
Poiché Java non supporta i tipi non firmati, i tipi HIDL non firmati vengono mappati al corrispondente tipo Java firmato. Le strutture vengono associate alle classi Java; gli array vengono associati agli array Java; le unioni non sono attualmente supportate in Java. Le stringhe vengono memorizzate internamente come UTF8. Poiché Java supporta solo le stringhe UTF-16, i valori delle stringhe inviati a o da un'implementazione Java vengono tradotti e potrebbero non essere identici alla nuova traduzione, poiché gli insiemi di caratteri non vengono sempre mappati in modo uniforme.
I dati ricevuti tramite IPC in C++ sono contrassegnati come const
e si trovano in una memoria di sola lettura che persiste solo per la durata della chiamata della funzione. I dati ricevuti tramite IPC in Java sono già stati copiati negli oggetti Java, quindi possono essere conservati senza ulteriori copie (e possono essere modificati).
Annotazioni
Le annotazioni in stile Java possono essere aggiunte alle dichiarazioni di tipo. Le annotazioni vengono analizzate dal backend Vendor Test Suite (VTS) del compilatore HIDL, ma nessuna di queste annotazioni analizzate viene effettivamente compresa dal compilatore HIDL. Al contrario, le annotazioni VTS analizzate vengono gestite dal compilatore VTS (VTSC).
Le annotazioni utilizzano la sintassi Java: @annotation
o
@annotation(value)
o @annotation(id=value, id=value…)
dove il valore può essere un'espressione costante, una stringa o un elenco di valori
all'interno di {}
, proprio come in Java. È possibile associare più annotazioni con lo stesso nome allo stesso elemento.
Dichiarazioni forward
In HIDL, le strutture potrebbero non essere dichiarate in avanti, rendendo impossibili i tipi di dati autoreferenziali definiti dall'utente (ad esempio, non puoi descrivere una lista concatenata o un albero in HIDL). La maggior parte degli HAL esistenti (pre-Android 8.x) ha un utilizzo limitato delle dichiarazioni forward, che possono essere rimosse riordinando le dichiarazioni della struttura di dati.
Questa limitazione consente di copiare le strutture di dati per valore con una semplice copia approfondita, anziché tenere traccia dei valori del puntatore che potrebbero verificarsi più volte in una struttura di dati autoriferita. Se gli stessi dati vengono passati due volte, ad esempio con due parametri di metodo o vec<T>
che rimandano agli stessi dati, vengono create e inviate due copie distinte.
Dichiarazioni nidificate
HIDL supporta dichiarazioni nidificate per tutti i livelli che vuoi (con una sola eccezione indicata di seguito). Ad esempio:
interface IFoo { uint32_t[3][4][5][6] multidimArray; vec<vec<vec<int8_t>>> multidimVector; vec<bool[4]> arrayVec; struct foo { struct bar { uint32_t val; }; bar b; } struct baz { foo f; foo.bar fb; // HIDL uses dots to access nested type names } …
L'eccezione è che i tipi di interfaccia possono essere incorporati solo in
vec<T>
e solo a un livello di profondità (no
vec<vec<IFoo>>
).
Sintassi del puntatore non elaborato
Il linguaggio HIDL non utilizza * e non supporta la piena flessibilità dei puntatori non elaborati C/C++. Per informazioni dettagliate su come HIDL incapsula i puntatori e gli array/vettori, consulta il modello vec<T>.
Interfacce
La parola chiave interface
ha due utilizzi.
- Apre la definizione di un'interfaccia in un file .hal.
- Può essere utilizzato come tipo speciale nei campi struct/union, nei parametri dei metodi e nei valori restituiti. È considerata un'interfaccia generale e un sinonimo di
android.hidl.base@1.0::IBase
.
Ad esempio, IServiceManager
ha il seguente metodo:
get(string fqName, string name) generates (interface service);
Il metodo promette di cercare qualche interfaccia per nome. È anche identico all'interfaccia di sostituzione con android.hidl.base@1.0::IBase
.
Le interfacce possono essere passate solo in due modi: come parametri di primo livello o come membri di un vec<IMyInterface>
. Non possono essere membri di vettori, strutture, array o unioni nidificati.
MQDescriptorSync e MQDescriptorUnsync
I tipi MQDescriptorSync
e MQDescriptorUnsync
trasmettono descrittori FMQ (Fast Message Queue) sincronizzati o non sincronizzati
tramite un'interfaccia HIDL. Per maggiori dettagli, consulta
HIDL C++ (le FMQ non sono supportate in Java).
tipo di memoria
Il tipo memory
viene utilizzato per rappresentare la memoria condivisa non mappata in HIDL. È supportato solo in C++. Un valore di questo tipo può essere utilizzato sul lato di ricezione per inizializzare un oggetto IMemory
, mappando la memoria e rendendola utilizzabile. Per maggiori dettagli, consulta
HIDL C++.
Avviso:i dati strutturati inseriti nella memoria condivisa DEVONO essere di un tipo il cui formato non cambia mai per tutta la durata della versione dell'interfaccia che passa il memory
. In caso contrario, gli HAL possono avere problemi di compatibilità fatali.
tipo di cursore
Il tipo pointer
è solo per uso interno HIDL.
Modello di tipo bitfield<T>
bitfield<T>
in cui T
è un
enum definito dall'utente suggerisce che il valore è un OR bitwise dei
valori enum definiti in T
. Nel codice generato,bitfield<T>
viene visualizzato come il tipo sottostante di T. Per
esempio:
enum Flag : uint8_t { HAS_FOO = 1 << 0, HAS_BAR = 1 << 1, HAS_BAZ = 1 << 2 }; typedef bitfield<Flag> Flags; setFlags(Flags flags) generates (bool success);
Il compilatore gestisce il tipo Flags nello stesso modo di uint8_t
.
Perché non utilizzare
(u)int8_t
/(u)int16_t
/(u)int32_t
/(u)int64_t
?
L'utilizzo di bitfield
fornisce ulteriori informazioni HAL al lettore,
che ora sa che setFlags
accetta un valore OR bit di Flag (ovvero
sa che chiamare setFlags
con 16 non è valido). Senza
bitfield
, queste informazioni vengono trasmesse solo tramite documentazione. Inoltre, il VTS può verificare se il valore dei flag è un OR bitwise di Flag.
Maniglie di tipo primitivo
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 potrebbero causare accesso alla memoria o danneggiamento della memoria.
La semantica HIDL è di tipo copia per valore, il che implica che i parametri vengono copiati.
Eventuali parti di dati di grandi dimensioni o dati che devono essere condivisi tra i processi (ad esempio una recinzione di sincronizzazione) vengono gestiti passando descrittori file che rimandano a oggetti permanenti: ashmem
per la memoria condivisa, i file effettivi o qualsiasi altro elemento che può nascondersi dietro un descrittore file. Il driver del binder
duplica il descrittore file nell'altro processo.
native_handle_t
Android supporta native_handle_t
, un concetto generale di handle
definito in libcutils
.
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;
Un handle nativo è una raccolta di interi e descrittori di file che viene passata per valore. Un singolo descrittore file può essere memorizzato in un handle nativo senza interi e un singolo descrittore file. Il passaggio di handle utilizzando handle nativi concapsulati con il tipo primitivo handle
garantisce che gli handle nativi siano inclusi direttamente in HIDL.
Poiché un native_handle_t
ha dimensioni variabili, non può essere incluso
direttamente in una struct. Un campo handle genera un puntatore a un native_handle_t
allocato separatamente.
Nelle versioni precedenti di Android, gli handle nativi venivano creati utilizzando le stesse funzioni presenti in libcutils.
In Android 8.0 e versioni successive, queste funzioni vengono ora copiate nello spazio dei nomi android::hardware::hidl
o spostate nell'NDK. Il codice HIDL
generato automaticamente esegue la serializzazione e la deserializzazione di queste funzioni automaticamente,
senza il coinvolgimento del codice scritto dall'utente.
Proprietà di handle e descrittori file
Quando chiami un metodo dell'interfaccia HIDL che passa (o restituisce) un oggetto hidl_handle
(di primo livello o parte di un tipo composto), la proprietà dei descrittori file in esso contenuti è la seguente:
- L'autore della chiamata che passa un oggetto
hidl_handle
come argomento mantiene la proprietà dei descrittori file contenuti nelnative_handle_t
che racchiude. L'autore della chiamata deve chiudere questi descrittori file al termine del loro utilizzo. - Il processo che restituisce un oggetto
hidl_handle
(passandolo a una funzione_cb
) mantiene la proprietà dei descrittori file contenuti nelnative_handle_t
racchiuso dall'oggetto. Il processo deve chiudere questi descrittori file al termine del loro utilizzo. - Un trasporto che riceve un
hidl_handle
ha la proprietà dei descrittori file all'interno delnative_handle_t
avvolto dall'oggetto. Il destinatario può utilizzare questi descrittori file così come sono durante il callback della transazione, ma deve clonare l'handle nativo per utilizzarli al di fuori del callback. Il trasporto chiama automaticamenteclose()
per i descrittori file al termine della transazione.
HIDL non supporta i handle in Java (poiché Java non supporta affatto i handle).
Array con dimensioni
Per gli array di dimensioni nelle struct HIDL, i relativi elementi possono essere di qualsiasi tipo che una struct può contenere:
struct foo { uint32_t[3] x; // array is contained in foo };
Stringhe
Le stringhe vengono visualizzate in modo diverso in C++ e Java, ma il tipo di archiviazione del trasporto sottostante è una struttura C++. Per maggiori dettagli, consulta Tipi di dati HIDL C++ o Tipi di dati HIDL Java.
Nota:il passaggio di una stringa a o da Java tramite un'interfaccia HIDL (incluso Java a Java) causa conversioni dei set di caratteri che potrebbero non preservare la codifica originale.
Modello di tipo vec<T>
Il modello vec<T>
rappresenta un buffer di dimensioni variabili contenente istanze di T
.
T
può essere uno dei seguenti:
- Tipi primitivi (ad es. uint32_t)
- Stringhe
- Enum definiti dall'utente
- Strutture definite dall'utente
- Interfacce o la parola chiave
interface
(vec<IFoo>
,vec<interface>
è supportato solo come parametro di primo livello) - Handle
- bitfield<U>
- vec<U>, dove U è in questo elenco, tranne l'interfaccia (ad es.
vec<vec<IFoo>>
non è supportato) - U[] (array di U di dimensioni), dove U è in questo elenco, tranne che per l'interfaccia
Tipi definiti dall'utente
Questa sezione descrive i tipi definiti dall'utente.
Enum
HIDL non supporta gli enum anonimi. In caso contrario, gli enum in HIDL sono simili a C++11:
enum name : type { enumerator , enumerator = constexpr , … }
Un enum di base è definito in termini di uno dei tipi di interi in HIDL. Se non viene specificato alcun valore per il primo enumeratore di un enum basato su un tipo intero, il valore predefinito è 0. Se non viene specificato alcun valore per un enumeratore successivo, il valore predefinito è il valore precedente più uno. Ad esempio:
// RED == 0 // BLUE == 4 (GREEN + 1) enum Color : uint32_t { RED, GREEN = 3, BLUE }
Un enum può anche ereditare da un enum definito in precedenza. Se non viene specificato alcun valore per il primo enumeratore di un enum secondario (in questo caso FullSpectrumColor
), viene utilizzato per impostazione predefinita il valore dell'ultimo enumeratore dell'enum principale più uno. Ad esempio:
// ULTRAVIOLET == 5 (Color:BLUE + 1) enum FullSpectrumColor : Color { ULTRAVIOLET }
Avviso: l'eredità degli enum funziona in modo inverso rispetto alla maggior parte degli altri tipi di ereditarietà. Un valore enumerato secondario non può essere utilizzato come valore enumerato principale. Questo perché un enum figlio include più valori rispetto all'enum primario. Tuttavia, un valore enumerato principale può essere utilizzato in sicurezza come valore enumerato secondario perché i valori enumerati secondari sono per definizione un superset dei valori enumerati principali. Tieni presente questo aspetto quando progetti le interfacce, in quanto i tipi che fanno riferimento agli enumerati principali non possono fare riferimento agli enumerati secondari nelle iterazioni successive dell'interfaccia.
I valori degli enum vengono richiamati con la sintassi dei due punti (non con la sintassi dei punti come per i tipi nidificati). La sintassi è Type:VALUE_NAME
. Non è necessario specificare il tipo se il valore a cui si fa riferimento è dello stesso tipo di enum o dei tipi figlio. Esempio:
enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 }; enum Color : Grayscale { RED = WHITE + 1 }; enum Unrelated : uint32_t { FOO = Color:RED + 1 };
A partire da Android 10, gli enum hanno un attributo len
che può essere utilizzato nelle espressioni costanti.
MyEnum::len
è il numero totale di voci nell'enumerazione.
È diverso dal numero totale di valori, che potrebbe essere inferiore se i valori sono duplicati.
Struct
HIDL non supporta le strutture anonime. In caso contrario, le strutture in HIDL sono molto simili a quelle in C.
HIDL non supporta strutture di dati di lunghezza variabile contenute interamente all'interno di una struct. Sono inclusi gli array di lunghezza indeterminata che a volte vengono utilizzati come
ultimo campo di una struttura in C/C++ (a volte con una dimensione di
[0]
). HIDL vec<T>
rappresenta array di dimensioni dinamiche con i dati memorizzati in un buffer separato; queste istanze sono rappresentate
con un'istanza di vec<T>
in struct
.
Analogamente, string
può essere contenuto in un struct
(i buffer associati sono separati). In C++, le istanze del tipo di handle HIDL sono rappresentate tramite un puntatore all'handle nativo effettivo poiché le istanze del tipo di dati sottostante sono di lunghezza variabile.
Union
HIDL non supporta le unioni anonime. In caso contrario, le unioni sono simili a C.
Le unioni non possono contenere tipi di correzione (ad esempio puntatori, descrittori file, oggetti binder). Non richiedono campi speciali o tipi associati e vengono semplicemente copiati utilizzando memcpy()
o un'opzione equivalente. Un'unione potrebbe non contenere direttamente (o contenere utilizzando altre strutture di dati) elementi che richiedono l'impostazione di offset del binder (ovvero handle o riferimenti all'interfaccia del binder). Ad esempio:
union UnionType { uint32_t a; // vec<uint32_t> r; // Error: can't contain a vec<T> uint8_t b;1 }; fun8(UnionType info); // Legal
Le unioni possono essere dichiarate anche all'interno di struct. Ad esempio:
struct MyStruct { union MyUnion { uint32_t a; uint8_t b; }; // declares type but not member union MyUnion2 { uint32_t a; uint8_t b; } data; // declares type but not member }