Types de données

Les déclarations de données HIDL génèrent des structures de données de présentation standard C++. Ces structures peuvent être placées n'importe où cela semble naturel (sur la pile, dans un fichier ou à l'échelle globale, ou sur le tas) et peuvent être composées de la même manière. Le code client appelle le code proxy HIDL en passant des références const et des types primitifs, tandis que le code stub et 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 les structures de données.

Le tableau ci-dessous mappe les primitives HIDL aux 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 les types de données plus en détail.

énumération

Une énumération en 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 chaque énumérateur dans l'ordre dans lequel il apparaît dans le code source HIDL, en commençant par l'énumération parent jusqu'au dernier enfant. Par exemple, ce code parcourt WRITE , READ , NONE et COMPARE dans cet ordre. Étant donné 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 inverses et peut être utilisé dans des contextes constexpr . Si une valeur apparaît plusieurs fois dans une énumération, la valeur apparaît plusieurs fois dans la plage.

champ de bits<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 comparable de taille fixe est hidl_array . Un hidl_vec<T> peut également être initialisé pour pointer vers un tampon de données externe de type T , en utilisant la fonction hidl_vec::setToExternal() .

En plus d'émettre/insérer la structure 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 à traduire vers/depuis std::vector et les pointeurs T nus. Si le vec<T> est utilisé comme paramètre, la fonction qui l'utilise sera surchargée (deux prototypes seront 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 constants dans hidl sont représentés par la classe hidl_array dans libhidlbase . Un hidl_array<T, S1, S2, …, SN> représente un tableau de taille fixe à N dimensions T[S1][S2]…[SN] .

chaîne

La classe hidl_string (qui fait partie de libhidlbase ) peut être utilisée pour transmettre des chaînes sur les 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 comment convertir vers et depuis std::string and char* (chaîne de style C) en utilisant operator= , les conversions implicites et la fonction .c_str() . Les structures de chaîne HIDL ont les constructeurs de copie et les opérateurs d'affectation appropriés pour :

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

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

structurer

Une struct en HIDL ne peut contenir que des types de données de taille fixe et aucune fonction. Les définitions de structure HIDL correspondent directement aux struct de présentation standard en C++, garantissant que les struct ont une disposition de mémoire cohérente. Une structure peut inclure des types HIDL, notamment handle , string et vec<T> , qui pointent vers des tampons séparés de longueur variable.

poignée

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.

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 (cela 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 enveloppe. Il existe simplement pour stocker en toute sécurité un pointeur vers un native_handle_t de sorte qu'il puisse être utilisé dans les processus 32 et 64 bits.

Les scénarios dans lesquels hidl_handle possède ses descripteurs de fichiers inclus incluent :

  • Suite à 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 copie de construction à 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/depuis les objets native_handle_t* . L'utilisation principale du type handle dans HIDL est de transmettre des descripteurs de fichiers via les 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 vivent dans un processus différent, l'implémentation RPC prendra automatiquement en charge le descripteur de fichier pour garantir 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 persistera pas au-delà de la fonction de réception (il sera fermé une fois la fonction renvoyée). Un processus qui souhaite conserver un accès persistant au descripteur de fichier doit dup() les descripteurs de fichiers inclus ou copier l'intégralité de l'objet hidl_handle .

mémoire

Le type memory HIDL correspond à la classe hidl_memory dans libhidlbase , qui représente la mémoire partagée non mappée. C'est l'objet qui doit être transmis entre les processus pour partager la mémoire en 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 peut être utilisé pour 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 la 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 toutes les interfaces importées seront définies comme un type.

Les variables qui contiennent des interfaces doivent être des pointeurs forts : sp<IName> . Les fonctions HIDL qui prennent des paramètres d'interface convertiront les pointeurs bruts en pointeurs forts, provoquant 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<> .