Types de données

Les déclarations de données HIDL génèrent des structures de données de mise en page standard C++. Ces structures peuvent être placées n'importe où (sur la pile, au niveau du fichier ou de la portée globale, ou sur la pile) et peuvent être composées de la même manière. Le code client appelle le code de proxy HIDL en transmettant des références constantes et des types primitifs, tandis que le code de bouchon et de proxy masque les détails de la sérialisation.

Remarque:À aucun moment, le code écrit par le développeur n'est requis pour sérialiser ou désérialiser explicitement des structures de données.

Le tableau ci-dessous met en correspondance les primitives HIDL avec les types de données C++:

Type HIDL Type C++ En-tête/Bibliothèque
enum enum class
uint8_t..uint64_t uint8_t..uint64_t <stdint.h>
int8_t..int64_t int8_t..int64_t <stdint.h>
float float
double double
vec<T> hidl_vec<T> libhidlbase
T[S1][S2]...[SN] T[S1][S2]...[SN]
string hidl_string libhidlbase
handle hidl_handle libhidlbase
safe_union (custom) struct
struct struct
union union
fmq_sync MQDescriptorSync libhidlbase
fmq_unsync MQDescriptorUnsync libhidlbase

Les sections ci-dessous décrivent plus en détail les types de données.

enum

Une énumération dans HIDL devient une énumération en C++. Par exemple:

enum Mode : uint8_t { WRITE = 1 << 0, READ = 1 << 1 };
enum SpecialMode : Mode { NONE = 0, COMPARE = 1 << 2 };

… devient:

enum class Mode : uint8_t { WRITE = 1, READ = 2 };
enum class SpecialMode : uint8_t { WRITE = 1, READ = 2, NONE = 0, COMPARE = 4 };

À partir d'Android 10, une énumération peut être itérée à l'aide de ::android::hardware::hidl_enum_range. Cette plage inclut tous les énumérateurs dans l'ordre dans lequel ils apparaissent dans le code source HIDL, en commençant par l'énumération parente jusqu'au dernier enfant. Par exemple, ce code itère sur WRITE, READ, NONE et COMPARE dans cet ordre. Compte tenu de SpecialMode ci-dessus:

template <typename T>
using hidl_enum_range = ::android::hardware::hidl_enum_range<T>

for (SpecialMode mode : hidl_enum_range<SpecialMode>) {...}

hidl_enum_range implémente également des itérateurs inversés et peut être utilisé dans des contextes constexpr. Si une valeur apparaît plusieurs fois dans une énumération, elle apparaît plusieurs fois dans la plage.

bitfield<T>

bitfield<T> (où T est une énumération définie par l'utilisateur) devient le type sous-jacent de cette énumération en C++. Dans l'exemple ci-dessus, bitfield<Mode> devient uint8_t.

vec<T>

Le modèle de classe hidl_vec<T> fait partie de libhidlbase et peut être utilisé pour transmettre un vecteur de n'importe quel type HIDL avec une taille arbitraire. Le conteneur de taille fixe comparable est hidl_array. Un hidl_vec<T> peut également être initialisé pour pointer vers un tampon de données externe de type T, à l'aide de la fonction hidl_vec::setToExternal().

En plus d'émettre/d'insérer la struct de manière appropriée dans l'en-tête C++ généré, l'utilisation de vec<T> génère des fonctions pratiques pour traduire depuis/vers std::vector et les pointeurs T nus. Si vec<T> est utilisé comme paramètre, la fonction qui l'utilise est surchargée (deux prototypes sont générés) pour accepter et transmettre à la fois la structure HIDL et un type std::vector<T> pour ce paramètre.

tableau

Les tableaux de constantes dans hidl sont représentés par la classe hidl_array dans libhidlbase. Un hidl_array<T, S1, S2, …, SN> représente un tableau T[S1][S2]…[SN] à taille fixe à N dimensions.

string

La classe hidl_string (qui fait partie de libhidlbase) peut être utilisée pour transmettre des chaînes via des interfaces HIDL et est définie dans /system/libhidl/base/include/hidl/HidlSupport.h. Le premier emplacement de stockage de la classe est un pointeur vers son tampon de caractères.

hidl_string sait convertir vers et depuis std::string and char* (chaîne de style C) à l'aide de operator=, de castings implicites et de la fonction .c_str(). Les structures de chaîne HIDL disposent des constructeurs de copie et des opérateurs d'affectation appropriés pour:

  • Chargez la chaîne HIDL à partir d'une chaîne std::string ou C.
  • Créez une std::string à partir d'une chaîne HIDL.

De plus, les chaînes HIDL disposent de constructeurs de conversion afin que les chaînes C (char *) et C++ (std::string) puissent être utilisées sur les méthodes qui acceptent une chaîne HIDL.

struct

Un struct dans HIDL ne peut contenir que des types de données de taille fixe et aucune fonction. Les définitions de struct HIDL sont mappées directement sur les struct de mise en page standard en C++, ce qui garantit que les struct ont une mise en page de mémoire cohérente. Une struct peut inclure des types HIDL, y compris handle, string et vec<T>, qui pointent vers des tampons de longueur variable distincts.

poignée

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 mauvaises poignées peuvent entraîner un accès incorrect à la mémoire ou une corruption de la mémoire.

Le type handle est représenté par la structure hidl_handle en C++, qui est un simple wrapper autour d'un pointeur vers un objet const native_handle_t (il est présent dans Android depuis longtemps).

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;

Par défaut, hidl_handle ne s'approprie pas le pointeur native_handle_t qu'il encapsule. Il existe simplement pour stocker de manière sécurisée un pointeur vers un native_handle_t afin qu'il puisse être utilisé à la fois dans les processus 32 bits et 64 bits.

Voici des exemples de scénarios dans lesquels hidl_handle possède ses descripteurs de fichiers inclus:

  • Après un appel à la méthode setTo(native_handle_t* handle, bool shouldOwn) avec le paramètre shouldOwn défini sur true
  • Lorsque l'objet hidl_handle est créé par construction par copie à partir d'un autre objet hidl_handle
  • Lorsque l'objet hidl_handle est attribué par copie à partir d'un autre objet hidl_handle

hidl_handle fournit des conversions implicites et explicites vers/à partir d'objets native_handle_t* . L'utilisation principale du type handle dans HIDL consiste à transmettre des descripteurs de fichiers via des interfaces HIDL. Un seul descripteur de fichier est donc représenté par un native_handle_t sans int et un seul fd. Si le client et le serveur résident dans un processus différent, l'implémentation du RPC s'occupe automatiquement du descripteur de fichier pour s'assurer que les deux processus peuvent fonctionner sur le même fichier.

Bien qu'un descripteur de fichier reçu dans un hidl_handle par un processus soit valide dans ce processus, il ne persiste pas au-delà de la fonction réceptrice (il est fermé lorsque la fonction renvoie). Un processus qui souhaite conserver un accès persistant au descripteur de fichier doit dup() les descripteurs de fichiers inclus ou copier l'objet hidl_handle entier.

mémoire

Le type memory HIDL est mappé sur la classe hidl_memory dans libhidlbase, qui représente la mémoire partagée non mappée. Il s'agit de l'objet qui doit être transmis entre les processus pour partager la mémoire dans HIDL. Pour utiliser la mémoire partagée:

  1. Obtenez une instance de IAllocator (actuellement, seule l'instance "ashmem" est disponible) et utilisez-la pour allouer de la mémoire partagée.
  2. IAllocator::allocate() renvoie un objet hidl_memory qui peut être transmis via HIDL RPC et mappé dans un processus à l'aide de la fonction mapMemory de libhidlmemory.
  3. mapMemory renvoie une référence à un objet sp<IMemory> qui permet d'accéder à la mémoire. (IMemory et IAllocator sont définis dans android.hidl.memory@1.0.)

Une instance de IAllocator peut être utilisée pour allouer de la mémoire:

#include <android/hidl/allocator/1.0/IAllocator.h>
#include <android/hidl/memory/1.0/IMemory.h>
#include <hidlmemory/mapping.h>
using ::android::hidl::allocator::V1_0::IAllocator;
using ::android::hidl::memory::V1_0::IMemory;
using ::android::hardware::hidl_memory;
....
  sp<IAllocator> ashmemAllocator = IAllocator::getService("ashmem");
  ashmemAllocator->allocate(2048, [&](bool success, const hidl_memory& mem) {
        if (!success) { /* error */ }
        // now you can use the hidl_memory object 'mem' or pass it around
  }));

Les modifications réelles de la mémoire doivent être effectuées via un objet IMemory, soit du côté qui a créé mem, soit du côté qui le reçoit via HIDL RPC.

// Same includes as above

sp<IMemory> memory = mapMemory(mem);
void* data = memory->getPointer();
memory->update();
// update memory however you wish after calling update and before calling commit
data[0] = 42;
memory->commit();
// …
memory->update(); // the same memory can be updated multiple times
// …
memory->commit();

interface

Les interfaces peuvent être transmises en tant qu'objets. Le mot interface peut être utilisé comme sucre syntaxique pour le type android.hidl.base@1.0::IBase. De plus, l'interface actuelle et les interfaces importées sont définies comme un type.

Les variables qui contiennent des interfaces doivent être des pointeurs forts : sp<IName>. Les fonctions HIDL qui acceptent des paramètres d'interface convertissent les pointeurs bruts en pointeurs forts, ce qui entraîne un comportement non intuitif (le pointeur peut être effacé de manière inattendue). Pour éviter les problèmes, stockez toujours les interfaces HIDL en tant que sp<>.