Types de données

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

Les similitudes avec le C++ incluent :

  • structs utilisent la syntaxe C++ ; unions prennent en charge la syntaxe C++ par défaut. Les deux doivent être nommés ; les structures et les unions anonymes ne sont pas prises en charge.
  • Les typedefs sont autorisés en HIDL (comme ils le sont en C++).
  • Les commentaires de style C++ sont autorisés et sont copiés dans le fichier d'en-tête généré.

Les similitudes avec Java incluent :

  • 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 tableau 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 similaires au format javadoc.

Représentation des données

Une struct ou union composée de Standard-Layout (un sous-ensemble de l'exigence des types de données simples) a une disposition 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 aux types C++ standard tels que std::uint32_t de cstdint .

Comme Java ne prend pas en charge les types non signés, les types HIDL non signés sont mappés au type Java signé correspondant. Les structures sont mappées aux classes Java ; les tableaux sont mappés aux tableaux Java ; les syndicats ne sont actuellement pas pris en charge en Java. Les chaînes sont stockées en interne au format UTF8. Étant donné que Java ne prend en charge que les chaînes UTF16, 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 retraduction, car les jeux de caractères ne sont pas toujours mappés de manière fluide.

Les données reçues via 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 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

Des annotations de style Java peuvent être ajoutées aux déclarations de type. Les annotations sont analysées par le backend Vendor Test Suite (VTS) du compilateur HIDL, mais aucune de ces annotations analysées n'est réellement comprise par le compilateur HIDL. Au lieu de cela, 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ù value peut être soit une expression constante, une chaîne ou une liste de valeurs à l'intérieur {} , comme dans Java. Plusieurs annotations du même nom peuvent être attachées au même élément.

Déclarations à terme

En HIDL, les structures ne peuvent pas être déclarées en aval, ce qui rend impossibles 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 ou une arborescence en HIDL). La plupart des HAL existantes (avant Android 8.x) ont une utilisation limitée des déclarations directes, qui peuvent être supprimées en réorganisant les déclarations de structure de données.

Cette restriction permet aux structures de données d'être copiées par valeur avec une simple copie complète, plutôt que de garder une trace des valeurs de pointeur qui peuvent apparaître 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 vec<T> qui pointent vers les mêmes données, deux copies distinctes sont créées et livrées.

Déclarations imbriquées

HIDL prend en charge les déclarations imbriquées à autant de niveaux que vous le souhaitez (à une exception près notée ci-dessous). Par 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 que dans vec<T> et à un seul niveau de profondeur (pas de vec<vec<IFoo>> ).

Syntaxe du pointeur brut

Le langage HIDL n'utilise pas * et ne prend pas en charge toute la flexibilité des pointeurs bruts C/C++. Pour plus de détails sur la façon dont HIDL encapsule les pointeurs et les tableaux/vecteurs, consultez vec<T> template .

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 struct/union, les paramètres de méthode et les retours. Il est considéré comme une interface générale et synonyme de android.hidl.base@1.0::IBase .

Par exemple, IServiceManager a la méthode suivante :

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

La méthode promet de rechercher une interface par son nom. Il est également identique de 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 niveau supérieur ou en tant que membres d'un vec<IMyInterface> . Ils ne peuvent pas être membres de vecs, de structures, de tableaux ou d'unions imbriqués.

MQDescriptorSync et MQDescriptorUnsync

Les types MQDescriptorSync et MQDescriptorUnsync transmettent des descripteurs Fast Message Queue (FMQ) synchronisés ou non via une interface HIDL. Pour plus de détails, consultez HIDL C++ (les FMQ ne sont pas pris en charge en Java).

type de mémoire

Le type memory est utilisé pour représenter la mémoire partagée non mappée dans HIDL. Il n'est pris en charge qu'en C++. Une valeur de ce type peut être utilisée du côté récepteur pour initialiser un objet IMemory , mapper la mémoire et la rendre utilisable. Pour plus de détails, consultez HIDL C++ .

Attention : les données structurées placées en mémoire partagée DOIVENT être d'un type dont le format ne changera jamais pendant toute la durée de vie de la version de l'interface passant en memory . Sinon, les HAL pourraient souffrir de problèmes de compatibilité fatals.

type de pointeur

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

modèle de type champ de bits<T>

bitfield<T> dans lequel T est une énumération définie par l'utilisateur suggère que la valeur est un OU au niveau du bit 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. Par 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 bitfield fournit des informations HAL supplémentaires au lecteur, qui sait maintenant que setFlags prend une valeur OR au niveau du bit de Flag (c'est-à-dire qu'il sait que l'appel setFlags avec 16 n'est pas valide). Sans bitfield , ces informations sont transmises uniquement via la documentation. De plus, VTS peut réellement vérifier si la valeur des indicateurs est un OU au niveau du bit de Flag.

gérer le type primitif

AVERTISSEMENT : les adresses de toute nature (même les adresses de périphériques physiques) ne doivent jamais faire partie d'un handle natif. La transmission de ces informations entre processus est dangereuse 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 au sein d'un processus. Sinon, de mauvais handles peuvent entraîner un mauvais accès à la mémoire ou une corruption de la mémoire.

La sémantique HIDL est copie par valeur, ce qui implique que les paramètres sont copiés. Toutes les données volumineuses, ou les données qui doivent être partagées entre les processus (comme une clôture 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 classeur duplique le descripteur de fichier dans l'autre processus.

native_handle_t

Android prend en charge native_handle_t , un concept général de handle 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 handle natif sans entier et avec un seul descripteur de fichier. Le passage de handles à l’aide de handles natifs encapsulés avec le type primitif handle garantit que les handles natifs sont directement inclus dans HIDL.

Comme un native_handle_t a une taille variable, il ne peut pas être inclus directement dans une structure. Un champ handle génère un pointeur vers un native_handle_t alloué séparément.

Dans les versions antérieures d'Android, les handles natifs étaient créés en utilisant les mêmes fonctions présentes dans libcutils . Dans 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 HIDL généré automatiquement sérialise et désérialise ces fonctions automatiquement, sans implication du code écrit par l'utilisateur.

Propriété du handle et du descripteur de fichier

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 passant un objet hidl_handle comme argument conserve la propriété des descripteurs de fichiers contenus dans le native_handle_t qu'il enveloppe ; l'appelant doit fermer ces descripteurs de fichiers lorsqu'il en a terminé avec eux.
  • Le processus renvoyant un objet hidl_handle (en le passant dans une fonction _cb ) conserve la propriété des descripteurs de fichiers contenus dans le native_handle_t enveloppé par l'objet ; le processus doit fermer ces descripteurs de fichiers lorsqu'il en a terminé avec eux.
  • Un transport qui reçoit un hidl_handle est propriétaire des descripteurs de fichiers à l'intérieur du native_handle_t enveloppé par l'objet ; le récepteur peut utiliser ces descripteurs de fichiers tels quels lors du rappel de transaction, mais doit cloner le handle natif pour utiliser les descripteurs de fichiers au-delà du rappel. Le transport close() les descripteurs de fichiers une fois la transaction terminée.

HIDL ne prend pas en charge les handles en Java (car Java ne prend pas du tout en charge les handles).

Tableaux dimensionnés

Pour les tableaux dimensionnés 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
};

Cordes

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 plus de détails, consultez Types de données HIDL C++ ou Types de données HIDL Java .

Remarque : Le passage d'une chaîne vers ou depuis Java via une interface HIDL (y compris Java vers Java) entraînera des conversions de jeux de caractères qui pourraient ne pas préserver exactement 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'un des éléments suivants :

  • Types primitifs (par exemple uint32_t)
  • Cordes
  • Énumérations définies par l'utilisateur
  • Structures définies par l'utilisateur
  • Interfaces, ou le mot-clé interface ( vec<IFoo> , vec<interface> est pris en charge uniquement en tant que paramètre de niveau supérieur)
  • Poignées
  • champ de bits<U>
  • vec<U>, où U est dans cette liste sauf l'interface (par exemple, vec<vec<IFoo>> n'est pas pris en charge)
  • U[] (tableau dimensionné de U), où U est dans cette liste sauf l'interface

Types définis par l'utilisateur

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

Énumération

HIDL ne prend pas en charge les énumérations anonymes. Sinon, les énumérations en 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 entiers de HIDL. Si aucune valeur n'est spécifiée pour le premier énumérateur d'une énumération basée sur un type 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. Par 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 la valeur du dernier énumérateur de l'énumération parent plus un. Par exemple:

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

Attention : l'héritage Enum fonctionne à l'envers par rapport à 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 parent. En effet, une énumération enfant comprend plus de valeurs que le parent. Cependant, une valeur d'énumération parent peut être utilisée en toute sécurité comme valeur d'énumération enfant, car les valeurs d'énumération enfants sont par définition un sur-ensemble de valeurs d'énumération parent. Gardez cela à l'esprit lors de la conception d'interfaces, car cela signifie que les types faisant référence aux énumérations parents ne peuvent pas faire référence aux énumérations enfants dans les itérations ultérieures de votre interface.

Les valeurs des énumérations sont référencées avec la syntaxe deux-points (et non avec la syntaxe point comme types imbriqués). La syntaxe est Type:VALUE_NAME . Pas besoin de spécifier le 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 ont un attribut len ​​qui peut être utilisé dans des expressions constantes. MyEnum::len est le nombre total d'entrées dans cette énumération. Ce nombre est différent du nombre total de valeurs, qui peut être inférieur lorsque les valeurs sont dupliquées.

Structure

HIDL ne prend pas en charge les structures anonymes. Sinon, les structures en HIDL sont très similaires à celles du C.

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

De même, string peut être contenue dans une struct (les tampons associés sont séparés). Dans le C++ généré, les instances du type de handle HIDL sont représentées via un pointeur vers le handle natif réel, car les instances du type de données sous-jacent sont de longueur variable.

syndicat

HIDL ne soutient pas les syndicats anonymes. Sinon, les unions sont similaires à C.

Les unions ne peuvent pas contenir de types de correctifs (pointeurs, descripteurs de fichiers, objets classeur, etc.). Ils n'ont pas besoin de champs spéciaux ni de types associés et sont simplement copiés via memcpy() ou équivalent. Une union ne peut pas contenir directement (ou contenir via d'autres structures de données) quoi que ce soit qui nécessite la définition de décalages de classeur (c'est-à-dire des références de handle ou d'interface de classeur). Par 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 à l’intérieur des structures. Par 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
  }