Tipi di dati

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 nel native_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 nel native_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 del native_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
  }