Types de données

Cette section décrit les types de données HIDL. Pour en savoir plus sur l'implémentation, consultez HIDL C++ (pour les implémentations C++) ou HIDL Java (pour les implémentations Java).

Voici quelques similitudes avec C++:

  • structs utilise la syntaxe C++. unions est compatible avec la syntaxe C++ par défaut. Les deux doivent être nommés. Les structures et les unions anonymes ne sont pas acceptées.
  • Les types sont autorisés dans HIDL (comme dans C++).
  • Les commentaires de style C++ sont autorisés et sont copiés dans le fichier d'en-tête généré.

Voici quelques similitudes avec Java:

  • Pour chaque fichier, HIDL définit un espace de noms de style Java qui doit commencer par android.hardware.. L'espace de noms C++ généré est ::android::hardware::….
  • Toutes les définitions du fichier sont contenues dans un wrapper interface de style Java.
  • Les déclarations de tableaux HIDL suivent le style Java, et non le style C++. Exemple :
    struct Point {
        int32_t x;
        int32_t y;
    };
    Point[3] triangle;   // sized array
  • Les commentaires sont semblables au format Javadoc.

Représentation des données

Un struct ou union composé de Standard-Layout (un sous-ensemble des exigences des types de données simples) présente une mise en page de mémoire cohérente dans le code C++ généré, appliquée avec des attributs d'alignement explicites sur les membres struct et union.

Les types HIDL primitifs, ainsi que les types enum et bitfield (qui dérivent toujours de types primitifs) sont mappés sur des types C++ standards tels que std::uint32_t de cstdint.

Comme Java n'est pas compatible avec les types non signés, les types HIDL non signés sont mappés sur le type Java signé correspondant. Les structs sont mappées sur des classes Java, les tableaux sont mappés sur des tableaux Java et les unions ne sont pas actuellement compatibles avec Java. Les chaînes sont stockées en interne au format UTF8. Étant donné que Java n'accepte que les chaînes UTF-16, les valeurs de chaîne envoyées vers ou depuis une implémentation Java sont traduites et peuvent ne pas être identiques lors de la re-traduction, car les jeux de caractères ne sont pas toujours mappés de manière fluide.

Les données reçues via l'IPC en C++ sont marquées const et se trouvent dans une mémoire en lecture seule qui ne persiste que pendant la durée de l'appel de fonction. Les données reçues via l'IPC en Java ont déjà été copiées dans des objets Java. Elles peuvent donc être conservées sans copie supplémentaire (et peuvent être modifiées).

Annotations

Vous pouvez ajouter des annotations de style Java aux déclarations de type. Les annotations sont analysées par le backend de la suite de tests du fournisseur (VTS) du compilateur HIDL, mais aucune de ces annotations analysées n'est réellement comprise par le compilateur HIDL. À la place, les annotations VTS analysées sont gérées par le compilateur VTS (VTSC).

Les annotations utilisent la syntaxe Java: @annotation ou @annotation(value) ou @annotation(id=value, id=value…), où la valeur peut être une expression constante, une chaîne ou une liste de valeurs dans {}, comme en Java. Vous pouvez associer plusieurs annotations du même nom au même élément.

Déclarations de transfert

Dans HIDL, les structures peuvent ne pas être déclarées par avance, ce qui rend impossible les types de données auto-référentiels définis par l'utilisateur (par exemple, vous ne pouvez pas décrire une liste chaînée ni un arbre en HIDL). La plupart des HAL existants (antérieurs à Android 8.x) utilisent de manière limitée les déclarations anticipées, qui peuvent être supprimées en réorganisant les déclarations de structure de données.

Cette restriction permet de copier les structures de données par valeur avec une simple copie profonde, plutôt que de suivre les valeurs de pointeur qui peuvent se produire plusieurs fois dans une structure de données autoréférentielle. Si les mêmes données sont transmises deux fois, par exemple avec deux paramètres de méthode ou deux vec<T> qui pointent vers les mêmes données, deux copies distinctes sont créées et envoyées.

Déclarations imbriquées

HIDL accepte les déclarations imbriquées à autant de niveaux que vous le souhaitez (à une exception près, indiquée ci-dessous). Exemple :

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'exception est que les types d'interface ne peuvent être intégrés qu'à vec<T> et à un seul niveau (pas de vec<vec<IFoo>>).

Syntaxe du pointeur brut

Le langage HIDL n'utilise pas * et n'est pas compatible avec la flexibilité totale des pointeurs bruts C/C++. Pour en savoir plus sur l'encapsulation des pointeurs et des tableaux/vecteurs par HIDL, consultez le modèle vec<T>.

Interfaces

Le mot clé interface a deux utilisations.

  • Il ouvre la définition d'une interface dans un fichier .hal.
  • Il peut être utilisé comme type spécial dans les champs de struct/union, les paramètres de méthode et les valeurs renvoyées. Il est considéré comme une interface générale et synonyme de android.hidl.base@1.0::IBase.

Par exemple, IServiceManager possède la méthode suivante:

get(string fqName, string name) generates (interface service);

La méthode promet de rechercher une interface par nom. Il est également identique à remplacer l'interface par android.hidl.base@1.0::IBase.

Les interfaces ne peuvent être transmises que de deux manières: en tant que paramètres de premier niveau ou en tant que membres d'un vec<IMyInterface>. Ils ne peuvent pas être membres de vecteurs, de structures, de tableaux ou d'unions imbriqués.

MQDescriptorSync et MQDescriptorUnsync

Les types MQDescriptorSync et MQDescriptorUnsync transmettent des descripteurs de file d'attente de messages rapides (FMQ) synchronisés ou non synchronisés via une interface HIDL. Pour en savoir plus, consultez HIDL C++ (les files de messages de premier plan ne sont pas compatibles avec Java).

type de mémoire

Le type memory permet de représenter la mémoire partagée non mappée dans HIDL. Il n'est compatible qu'en C++. Une valeur de ce type peut être utilisée à l'extrémité réceptrice pour initialiser un objet IMemory, en mappant la mémoire et en la rendant utilisable. Pour en savoir plus, consultez HIDL C++.

Avertissement:Les données structurées placées dans la mémoire partagée DOIVENT être d'un type dont le format ne change jamais pendant la durée de vie de la version de l'interface transmettant le memory. Sinon, les HAL peuvent rencontrer des problèmes de compatibilité fatals.

type de pointeur

Le type pointer est réservé à un usage interne HIDL uniquement.

Modèle de type bitfield<T>

bitfield<T>, dans lequel T est une énumération définie par l'utilisateur, suggère que la valeur est une OR au niveau des bits des valeurs d'énumération définies dans T. Dans le code généré, bitfield<T> apparaît comme le type sous-jacent de T. Exemple :

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);

Le compilateur gère le type Flags de la même manière que uint8_t.

Pourquoi ne pas utiliser (u)int8_t/(u)int16_t/(u)int32_t/(u)int64_t ? L'utilisation de bitfield fournit des informations HAL supplémentaires au lecteur, qui sait maintenant que setFlags prend une valeur OR au niveau des bits de l'indicateur (c'est-à-dire qu'il sait que l'appel de setFlags avec 16 n'est pas valide). Sans bitfield, ces informations ne sont transmises que via la documentation. De plus, VTS peut vérifier si la valeur des indicateurs est une OR au niveau des bits de l'indicateur.

Poignées de type primitif

AVERTISSEMENT:Les adresses de quelque nature que ce soit (même les adresses d'appareils physiques) ne doivent jamais faire partie d'un handle natif. Transmettre ces informations entre les processus est dangereux et les rend vulnérables aux attaques. Toutes les valeurs transmises entre les processus doivent être validées avant d'être utilisées pour rechercher la mémoire allouée dans un processus. Sinon, de mauvais gestionnaires peuvent entraîner un accès incorrect à la mémoire ou une corruption de la mémoire.

La sémantique HIDL est basée sur la copie par valeur, ce qui implique que les paramètres sont copiés. Toutes les grandes quantités de données ou les données qui doivent être partagées entre les processus (telles qu'une barrière de synchronisation) sont gérées en transmettant des descripteurs de fichiers pointant vers des objets persistants: ashmem pour la mémoire partagée, les fichiers réels ou tout autre élément pouvant se cacher derrière un descripteur de fichier. Le pilote de liaison duplique le descripteur de fichier dans l'autre processus.

native_handle_t

Android est compatible avec native_handle_t, un concept de poignée général défini dans 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 natif est une collection d'entiers et de descripteurs de fichiers qui sont transmis par valeur. Un seul descripteur de fichier peut être stocké dans un gestionnaire natif sans ints et un seul descripteur de fichier. Transmettre des descripteurs à l'aide de descripteurs natifs encapsulés avec le type primitif handle garantit que les descripteurs natifs sont directement inclus dans HIDL.

Étant donné qu'un native_handle_t a une taille variable, il ne peut pas être inclus directement dans une struct. Un champ de poignée génère un pointeur vers un native_handle_t alloué séparément.

Dans les versions antérieures d'Android, les poignées natives étaient créées à l'aide des mêmes fonctions présentes dans libcutils. Sous Android 8.0 et versions ultérieures, ces fonctions sont désormais copiées dans l'espace de noms android::hardware::hidl ou déplacées vers le NDK. Le code généré automatiquement par HIDL sérialise et désérialise automatiquement ces fonctions, sans intervention du code écrit par l'utilisateur.

Propriété des poignées et des descripteurs de fichiers

Lorsque vous appelez une méthode d'interface HIDL qui transmet (ou renvoie) un objet hidl_handle (de niveau supérieur ou faisant partie d'un type composé), la propriété des descripteurs de fichiers qu'il contient est la suivante:

  • L'appelant qui transmet un objet hidl_handle en tant qu'argument conserve la propriété des descripteurs de fichiers contenus dans le native_handle_t qu'il encapsule. L'appelant doit fermer ces descripteurs de fichiers lorsqu'il n'en a plus besoin.
  • Le processus qui renvoie un objet hidl_handle (en le transmettant à une fonction _cb) conserve la propriété des descripteurs de fichiers contenus dans le native_handle_t encapsulé par l'objet. Le processus doit fermer ces descripteurs de fichiers lorsqu'il n'en a plus besoin.
  • Un transport qui reçoit un hidl_handle est propriétaire des descripteurs de fichiers dans le native_handle_t encapsulé par l'objet. Le destinataire peut utiliser ces descripteurs de fichiers tels quels lors du rappel de transaction, mais doit cloner le conteneur natif pour utiliser les descripteurs de fichiers au-delà du rappel. Le transport appelle automatiquement close() pour les descripteurs de fichiers une fois la transaction terminée.

HIDL n'est pas compatible avec les poignées en Java (car Java n'est pas du tout compatible avec les poignées).

Tableaux de taille fixe

Pour les tableaux de taille dans les structures HIDL, leurs éléments peuvent être de n'importe quel type qu'une structure peut contenir:

struct foo {
uint32_t[3] x; // array is contained in foo
};

Strings

Les chaînes apparaissent différemment en C++ et en Java, mais le type de stockage de transport sous-jacent est une structure C++. Pour en savoir plus, consultez les sections Types de données C++ HIDL ou Types de données Java HIDL.

Remarque:Transmettre une chaîne vers ou depuis Java via une interface HIDL (y compris Java vers Java) entraîne des conversions de jeu de caractères qui ne préservent pas nécessairement l'encodage d'origine.

Modèle de type vec<T>

Le modèle vec<T> représente un tampon de taille variable contenant des instances de T.

T peut être l'une des valeurs suivantes:

  • Types primitifs (par exemple, uint32_t)
  • Strings
  • énumérations définies par l'utilisateur ;
  • Structures définies par l'utilisateur
  • Interfaces ou mot clé interface (vec<IFoo>, vec<interface> n'est accepté que comme paramètre de niveau supérieur)
  • Identifiants
  • bitfield<U>
  • vec<U>, où U figure dans cette liste, sauf l'interface (par exemple, vec<vec<IFoo>> n'est pas pris en charge)
  • U[] (tableau de taille U), où U figure dans cette liste, sauf l'interface

Types définis par l'utilisateur

Cette section décrit les types définis par l'utilisateur.

Enum

HIDL n'est pas compatible avec les énumérations anonymes. Sinon, les énumérations dans HIDL sont similaires à C++11:

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

Une énumération de base est définie en termes de l'un des types d'entiers dans HIDL. Si aucune valeur n'est spécifiée pour le premier énumérateur d'une énumération basée sur un type d'entier, la valeur par défaut est 0. Si aucune valeur n'est spécifiée pour un énumérateur ultérieur, la valeur par défaut est la valeur précédente plus un. Exemple :

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

Une énumération peut également hériter d'une énumération précédemment définie. Si aucune valeur n'est spécifiée pour le premier énumérateur d'une énumération enfant (dans ce cas FullSpectrumColor), la valeur par défaut est celle du dernier énumérateur de l'énumération parente plus un. Exemple :

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

Avertissement:L'héritage d'énumération fonctionne à l'envers de la plupart des autres types d'héritage. Une valeur d'énumération enfant ne peut pas être utilisée comme valeur d'énumération parente. En effet, une énumération enfant inclut plus de valeurs que le parent. Toutefois, une valeur d'énumération parente peut être utilisée en toute sécurité comme valeur d'énumération enfant, car les valeurs d'énumération enfant sont par définition un sur-ensemble des valeurs d'énumération parente. Gardez cela à l'esprit lorsque vous concevez des interfaces, car cela signifie que les types faisant référence à des énumérations parentes ne peuvent pas faire référence à des énumérations enfants dans les itérations ultérieures de votre interface.

Les valeurs des énumérations sont référencées à l'aide de la syntaxe du deux-points (et non de la syntaxe du point pour les types imbriqués). La syntaxe est Type:VALUE_NAME. Vous n'avez pas besoin de spécifier de type si la valeur est référencée dans le même type d'énumération ou les mêmes types enfants. Exemple :

enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 };
enum Color : Grayscale { RED = WHITE + 1 };
enum Unrelated : uint32_t { FOO = Color:RED + 1 };

À partir d'Android 10, les énumérations disposent d'un attribut len qui peut être utilisé dans des expressions constantes. MyEnum::len correspond au nombre total d'entrées de cette énumération. Il s'agit d'une valeur différente du nombre total de valeurs, qui peut être inférieur lorsque des valeurs sont dupliquées.

Struct

HIDL n'est pas compatible avec les structures anonymes. Sinon, les structures dans HIDL sont très similaires à C.

HIDL n'est pas compatible avec les structures de données à longueur variable contenues entièrement dans une struct. Cela inclut le tableau à longueur indéfinie qui est parfois utilisé comme dernier champ d'une structure en C/C++ (parfois avec une taille de [0]). HIDL vec<T> représente des tableaux de taille dynamique avec les données stockées dans une mémoire tampon distincte. Ces instances sont représentées par une instance de vec<T> dans struct.

De même, string peut être contenu dans un struct (les tampons associés sont distincts). Dans le code C++ généré, les instances du type de poignée HIDL sont représentées via un pointeur vers la poignée native réelle, car les instances du type de données sous-jacent sont de longueur variable.

Union

HIDL n'est pas compatible avec les unions anonymes. Sinon, les unions sont semblables à C.

Les unions ne peuvent pas contenir de types de correction (tels que des pointeurs, des descripteurs de fichiers ou des objets de liaison). Ils n'ont pas besoin de champs spéciaux ni de types associés, et sont simplement copiés à l'aide de memcpy() ou d'un équivalent. Une union peut ne pas contenir directement (ou contenir à l'aide d'autres structures de données) tout élément nécessitant de définir des décalages de liaison (c'est-à-dire des références de poignée ou d'interface de liaison). Exemple :

union UnionType {
uint32_t a;
//  vec<uint32_t> r;  // Error: can't contain a vec<T>
uint8_t b;1
};
fun8(UnionType info); // Legal

Les unions peuvent également être déclarées dans des structs. Exemple :

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
  }