Tipi di dati

Questa sezione descrive i tipi di dati HIDL. Per i dettagli sull'implementazione, vedere HIDL C++ (per implementazioni C++) o HIDL Java (per implementazioni Java).

Le somiglianze con C++ includono:

  • structs utilizzano la sintassi C++; unions supportano la sintassi C++ per impostazione predefinita. Entrambi devono essere nominati; le strutture e le unioni anonime non sono supportate.
  • Le typedef sono consentite 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 di 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

Una struct o union composta da Standard-Layout (un sottoinsieme dei requisiti dei tipi di dati semplici) ha un layout di memoria coerente nel codice C++ generato, applicato con attributi di allineamento espliciti sui membri struct e union .

I tipi HIDL primitivi, così come i tipi enum e bitfield (che derivano sempre da tipi primitivi), vengono mappati a tipi C++ standard come std::uint32_t da cstdint .

Poiché Java non supporta i tipi non firmati, i tipi HIDL non firmati vengono mappati al tipo Java firmato corrispondente. Le strutture vengono mappate alle classi Java; gli array vengono mappati su array Java; le unioni non sono attualmente supportate in Java. Le stringhe vengono archiviate internamente come UTF8. Poiché Java supporta solo stringhe UTF16, i valori di stringa inviati a o da un'implementazione Java vengono tradotti e potrebbero non essere identici durante la ritraduzione poiché i set di caratteri non sempre vengono mappati in modo uniforme.

I dati ricevuti tramite IPC in C++ sono contrassegnati const e si trovano nella memoria di sola lettura che persiste solo per la durata della chiamata di 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

È possibile aggiungere annotazioni in stile Java 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. Invece, 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 value può essere un'espressione costante, una stringa o un elenco di valori all'interno di {} , proprio come in Giava. Allo stesso elemento possono essere allegate più annotazioni con lo stesso nome.

Dichiarazioni anticipate

In HIDL, le strutture potrebbero non essere dichiarate in avanti, rendendo impossibili i tipi di dati autoreferenziali e definiti dall'utente (ad esempio, non è possibile descrivere un elenco collegato o un albero in HIDL). La maggior parte degli HAL esistenti (precedenti ad Android 8.x) utilizzano in modo limitato le dichiarazioni anticipate, che possono essere rimosse riorganizzando le dichiarazioni della struttura dei dati.

Questa restrizione consente di copiare le strutture dati in base al valore con una semplice copia profonda, anziché tenere traccia dei valori dei puntatori che possono verificarsi più volte in una struttura dati autoreferenziale. Se gli stessi dati vengono passati due volte, ad esempio con due parametri del metodo o vec<T> che puntano agli stessi dati, vengono create e consegnate due copie separate.

Dichiarazioni nidificate

HIDL supporta dichiarazioni nidificate su tutti i livelli desiderati (con un'eccezione annotata di seguito). Per 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 grezzo

Il linguaggio HIDL non utilizza * e non supporta la flessibilità completa dei puntatori grezzi C/C++. Per dettagli su come HIDL incapsula puntatori e array/vettori, vedere vec<T> template .

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 di struttura/unione, parametri di metodo e ritorni. È visto come un'interfaccia generale e 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 alcune interfacce per nome. È anche identico sostituire l'interfaccia con android.hidl.base@1.0::IBase .

Le interfacce possono essere passate solo in due modi: come parametri di livello superiore o come membri di un vec<IMyInterface> . Non possono essere membri di vec, struct, array o unioni nidificate.

MQDescriptorSync e MQDescriptorUnsync

I tipi MQDescriptorSync e MQDescriptorUnsync passano un descrittore FMQ (Fast Message Queue) sincronizzato o non sincronizzato attraverso un'interfaccia HIDL. Per i dettagli, vedere HIDL C++ (gli FMQ non sono supportati in Java).

tipo di memoria

Il tipo di memory viene utilizzato per rappresentare la memoria condivisa non mappata in HIDL. È supportato solo in C++. Un valore di questo tipo può essere utilizzato dal lato ricevente per inizializzare un oggetto IMemory , mappando la memoria e rendendola utilizzabile. Per i dettagli, vedere HIDL C++ .

Avvertenza: i dati strutturati inseriti nella memoria condivisa DEVONO essere di un tipo il cui formato non cambierà mai per tutta la durata della versione dell'interfaccia che passa nella memory . In caso contrario, gli HAL potrebbero presentare problemi irreversibili di compatibilità.

tipo di puntatore

Il tipo pointer è solo per uso interno HIDL.

modello di tipo bitfield<T>

bitfield<T> in cui T è un'enumerazione definita dall'utente suggerisce che il valore è un OR bit per bit dei valori di enumerazione definiti in T . Nel codice generato, bitfield<T> viene visualizzato come tipo sottostante di T. Ad 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 allo stesso modo di uint8_t .

Perché non utilizzare (u)int8_t / (u)int16_t / (u)int32_t / (u)int64_t ? L'uso bitfield fornisce informazioni HAL aggiuntive al lettore, che ora sa che setFlags accetta un valore OR bit per bit di Flag (cioè sa che chiamare setFlags con 16 non è valido). Senza bitfield , queste informazioni vengono trasmesse solo tramite documentazione. Inoltre, VTS può effettivamente verificare se il valore dei flag è un OR bit per bit di Flag.

gestire il tipo primitivo

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.

La semantica HIDL è copia per valore, il che implica che i parametri vengono copiati. Eventuali porzioni di dati di grandi dimensioni, o dati che devono essere condivisi tra processi (come un recinto di sincronizzazione), vengono gestiti passando descrittori di file che puntano a oggetti persistenti: ashmem per memoria condivisa, file effettivi o qualsiasi altra cosa che possa nascondersi dietro un descrittore di file. Il driver del raccoglitore duplica il descrittore di file nell'altro processo.

native_handle_t

Android supporta native_handle_t , un concetto di handle generale 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 int e descrittori di file che vengono passati in base al valore. Un singolo descrittore di file può essere archiviato in un handle nativo senza int e un singolo descrittore di file. Il passaggio di handle utilizzando handle nativi incapsulati con il tipo primitivo handle garantisce che gli handle nativi siano inclusi direttamente in HIDL.

Poiché native_handle_t ha dimensioni variabili, non può essere incluso direttamente in una struttura. 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 libutils . 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 serializza e deserializza queste funzioni automaticamente, senza il coinvolgimento del codice scritto dall'utente.

Gestire e proprietà del descrittore di file

Quando chiami un metodo di interfaccia HIDL che passa (o restituisce) un oggetto hidl_handle (di livello superiore o parte di un tipo composto), la proprietà dei descrittori di file in esso contenuti è la seguente:

  • Il chiamante che passa un oggetto hidl_handle come argomento mantiene la proprietà dei descrittori di file contenuti nel native_handle_t che racchiude; il chiamante deve chiudere questi descrittori di file una volta terminato con essi.
  • Il processo che restituisce un oggetto hidl_handle (passandolo a una funzione _cb ) mantiene la proprietà dei descrittori di file contenuti nel native_handle_t racchiuso dall'oggetto; il processo deve chiudere questi descrittori di file una volta terminato con essi.
  • Un trasporto che riceve un hidl_handle ha la proprietà dei descrittori di file all'interno di native_handle_t racchiusi dall'oggetto; il destinatario può utilizzare questi descrittori di file così come sono durante la richiamata della transazione, ma deve clonare l'handle nativo per utilizzare i descrittori di file oltre la richiamata. Il trasporto close() i descrittori di file al termine della transazione.

HIDL non supporta gli handle in Java (poiché Java non supporta affatto gli handle).

Array dimensionati

Per gli array di dimensioni nelle strutture HIDL, i relativi elementi possono essere di qualsiasi tipo che una struttura 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 di trasporto sottostante è una struttura C++. Per i dettagli, consulta Tipi di dati HIDL C++ o Tipi di dati Java HIDL .

Nota: il passaggio di una stringa da o verso Java tramite un'interfaccia HIDL (incluso Java a Java) causerà conversioni del set di caratteri che potrebbero non preservare esattamente 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 esempio uint32_t)
  • stringhe
  • Enumerazioni definite dall'utente
  • Strutture definite dall'utente
  • Interfacce o la parola chiave interface ( vec<IFoo> , vec<interface> è supportata solo come parametro di livello superiore)
  • Maniglie
  • campo bit<U>
  • vec<U>, dove U è in questo elenco tranne l'interfaccia (ad esempio vec<vec<IFoo>> non è supportato)
  • U[] (array di dimensioni U), dove U è presente in questo elenco eccetto interfaccia

Tipi definiti dall'utente

Questa sezione descrive i tipi definiti dall'utente.

Enum

HIDL non supporta enumerazioni anonime. Altrimenti, le enumerazioni in HIDL sono simili a C++11:

enum name : type { enumerator , enumerator = constexpr , …  }

Un'enumerazione base è definita in termini di uno dei tipi interi in HIDL. Se non viene specificato alcun valore per il primo enumeratore di un'enumerazione basata 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. Per esempio:

// RED == 0
// BLUE == 4 (GREEN + 1)
enum Color : uint32_t { RED, GREEN = 3, BLUE }

Un'enumerazione può anche ereditare da un'enumerazione definita in precedenza. Se non viene specificato alcun valore per il primo enumeratore di un'enumerazione figlio (in questo caso FullSpectrumColor ), per impostazione predefinita viene utilizzato il valore dell'ultimo enumeratore dell'enumerazione padre più uno. Per esempio:

// ULTRAVIOLET == 5 (Color:BLUE + 1)
enum FullSpectrumColor : Color { ULTRAVIOLET }

Avvertenza: l'ereditarietà Enum funziona all'indietro rispetto alla maggior parte degli altri tipi di ereditarietà. Un valore enum figlio non può essere utilizzato come valore enum genitore. Questo perché un'enumerazione figlio include più valori rispetto al genitore. Tuttavia, un valore enum genitore può essere utilizzato in modo sicuro come valore enum figlio perché i valori enum figlio sono per definizione un superset di valori enum genitore. Tienilo presente quando progetti le interfacce poiché ciò significa che i tipi che fanno riferimento alle enumerazioni principali non possono fare riferimento alle enumerazioni secondarie nelle iterazioni successive dell'interfaccia.

I valori delle enumerazioni vengono indicati con la sintassi dei due punti (non con la sintassi del punto come tipi nidificati). La sintassi è Type:VALUE_NAME . Non è necessario specificare il tipo se si fa riferimento al valore nello stesso tipo enum o nei 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, le enumerazioni hanno un attributo len che può essere utilizzato nelle espressioni costanti. MyEnum::len è il numero totale di voci in tale enumerazione. Questo è diverso dal numero totale di valori, che potrebbe essere inferiore quando i valori vengono duplicati.

Struttura

HIDL non supporta strutture anonime. Altrimenti, le strutture in HIDL sono molto simili a C.

HIDL non supporta strutture di dati a lunghezza variabile contenute interamente all'interno di una struttura. Ciò include l'array di lunghezza indefinita che a volte viene usato come ultimo campo di una struttura in C/C++ (a volte visto con una dimensione [0] ). HIDL vec<T> rappresenta array di dimensioni dinamiche con i dati archiviati in un buffer separato; tali istanze sono rappresentate con un'istanza di vec<T> nella struct .

Allo stesso modo, string può essere contenuta in una struct (i buffer associati sono separati). Nel C++ generato, 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.

Unione

HIDL non supporta le unioni anonime. Altrimenti, i sindacati sono simili a C.

Le unioni non possono contenere tipi di correzione (puntatori, descrittori di file, oggetti raccoglitore e così via). Non necessitano di campi speciali o tipi associati e vengono semplicemente copiati tramite memcpy() o equivalente. Un'unione non può contenere direttamente (o contenere tramite altre strutture dati) nulla che richieda l'impostazione degli offset del raccoglitore (ad esempio, handle o riferimenti all'interfaccia del raccoglitore). Per 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 anche essere dichiarate all'interno delle strutture. Per 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
  }