Datentypen

HIDL-Datendeklarationen generieren C++-Datenstrukturen mit Standardlayout. Diese Strukturen können überall platziert werden, wo es sinnvoll ist (auf dem Stack, im Datei- oder globalen Gültigkeitsbereich oder auf dem Heap) und auf dieselbe Weise zusammengesetzt werden. Der Clientcode ruft HIDL-Proxycode auf, der Konstantenreferenzen und primitive Typen übergibt, während die Details der Serialization im Stub- und Proxycode ausgeblendet werden.

Hinweis:Es ist zu keinem Zeitpunkt erforderlich, von Entwicklern geschriebenen Code zu verwenden, um Datenstrukturen explizit zu serialisieren oder zu deserialisieren.

In der folgenden Tabelle werden HIDL-Primitive den C++-Datentypen zugeordnet:

HIDL-Typ C++-Typ Header/Bibliothek
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

In den folgenden Abschnitten werden Datentypen genauer beschrieben.

enum

Ein Enum in HIDL wird zu einem Enum in C++. Beispiel:

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

… wird zu:

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

Ab Android 10 kann eine Enum-Liste mit ::android::hardware::hidl_enum_range durchlaufen werden. Dieser Bereich umfasst jeden Enumerator in der Reihenfolge, in der er im HIDL-Quellcode erscheint, beginnend mit dem übergeordneten Enum bis hin zum letzten untergeordneten Element. In diesem Code wird beispielsweise in dieser Reihenfolge über WRITE, READ, NONE und COMPARE iteriert. Bei SpecialMode oben:

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

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

hidl_enum_range implementiert auch Rückwärtsiteratoren und kann in constexpr-Kontexten verwendet werden. Wenn ein Wert in einer Aufzählung mehrmals vorkommt, wird er auch im Bereich mehrmals berücksichtigt.

bitfield<T>

bitfield<T> (T ist ein benutzerdefiniertes Enum) wird zum zugrunde liegenden Typ dieses Enums in C++. Im obigen Beispiel wird bitfield<Mode> zu uint8_t.

vec<T>

Die hidl_vec<T>-Klassenvorlage ist Teil von libhidlbase und kann verwendet werden, um einen Vektor beliebigen HIDL-Typs mit beliebiger Größe zu übergeben. Der vergleichbare Container mit fester Größe ist hidl_array. Ein hidl_vec<T> kann auch mit der Funktion hidl_vec::setToExternal() initialisiert werden, um auf einen externen Datenpuffer vom Typ T zu verweisen.

Neben der korrekten Ausgabe/Einfügung des Structs in den generierten C++-Header generiert die Verwendung von vec<T> einige praktische Funktionen zum Umwandeln von/nach std::vector- und T-Pointern. Wenn vec<T> als Parameter verwendet wird, wird die Funktion, in der er verwendet wird, überladen (zwei Prototypen werden generiert), um sowohl die HIDL-Struktur als auch einen std::vector<T>-Typ für diesen Parameter zu akzeptieren und weiterzugeben.

Array

Konstante Arrays in hidl werden in libhidlbase durch die Klasse hidl_array dargestellt. Ein hidl_array<T, S1, S2, …, SN> steht für ein n-dimensionales Array mit fester GrößeT[S1][S2]…[SN].

String

Die Klasse hidl_string (Teil von libhidlbase) kann zum Übergeben von Strings über HIDL-Schnittstellen verwendet werden und ist in /system/libhidl/base/include/hidl/HidlSupport.h definiert. Der erste Speicherort in der Klasse ist ein Verweis auf den Zeichenpuffer.

hidl_string kann mithilfe von operator=, impliziten Umwandlungen und der Funktion .c_str() von und zu std::string and char* (String im C-Format) konvertieren. HIDL-String-Structs haben die entsprechenden Kopierkonstruktoren und Zuweisungsoperatoren für:

  • Laden Sie den HIDL-String aus einem std::string- oder C-String.
  • Erstellen Sie eine neue std::string aus einem HIDL-String.

Außerdem haben HIDL-Strings Konvertierungskonstruktoren, sodass C-Strings (char *) und C++-Strings (std::string) in Methoden verwendet werden können, die einen HIDL-String annehmen.

struct

Ein struct in HIDL kann nur Datentypen fester Größe und keine Funktionen enthalten. HIDL-Struct-Definitionen werden direkt structs mit Standardlayout in C++ zugeordnet, sodass structs ein einheitliches Speicherlayout haben. Ein STRUCT kann HIDL-Typen wie handle, string und vec<T> enthalten, die auf separate Puffer mit variabler Länge verweisen.

Alias

WARNUNG:Adressen jeglicher Art (auch physische Geräteadressen) dürfen niemals Teil eines nativen Handles sein. Die Weitergabe dieser Informationen zwischen Prozessen ist gefährlich und macht sie anfällig für Angriffe. Alle zwischen Prozessen übergebenen Werte müssen validiert werden, bevor sie zum Abrufen des zugewiesenen Arbeitsspeichers innerhalb eines Prozesses verwendet werden. Andernfalls können fehlerhafte Handles zu einem fehlerhaften Speicherzugriff oder zur Beschädigung des Arbeitsspeichers führen.

Der Typ handle wird in C++ durch die Struktur hidl_handle dargestellt, die ein einfacher Wrapper um einen Verweis auf ein const native_handle_t-Objekt ist. Dieser Typ ist schon lange in Android vorhanden.

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;

Standardmäßig übernimmt hidl_handle nicht die Inhaberschaft des native_handle_t-Zeigers, den es umschließt. Er dient lediglich dazu, einen Verweis auf eine native_handle_t sicher zu speichern, damit er sowohl in 32- als auch in 64-Bit-Prozessen verwendet werden kann.

Szenarien, in denen hidl_handle die zugehörigen Dateien-Beschreibungen besitzt:

  • Nach einem Aufruf der Methode setTo(native_handle_t* handle, bool shouldOwn), bei dem der Parameter shouldOwn auf true gesetzt ist
  • Wenn das hidl_handle-Objekt durch Kopierkonstruktion aus einem anderen hidl_handle-Objekt erstellt wird
  • Wenn das hidl_handle-Objekt von einem anderen hidl_handle-Objekt kopiert wird

hidl_handle bietet sowohl implizite als auch explizite Konvertierungen zu/von native_handle_t* -Objekten. Der Typ handle wird in HIDL hauptsächlich zum Übergeben von Dateideskriptoren über HIDL-Schnittstellen verwendet. Ein einzelner Dateideskriptor wird daher durch einen native_handle_t ohne ints und eine einzelne fd dargestellt. Wenn sich Client und Server in einem anderen Prozess befinden, kümmert sich die RPC-Implementierung automatisch um den Dateideskriptor, damit beide Prozesse auf dieselbe Datei zugreifen können.

Ein Dateideskriptor, der in einer hidl_handle von einem Prozess empfangen wird, ist in diesem Prozess zwar gültig, aber er bleibt nicht über die empfangende Funktion hinaus bestehen. Er wird geschlossen, wenn die Funktion zurückkehrt. Ein Prozess, der dauerhaften Zugriff auf den Dateideskriptor behalten möchte, muss die enthaltenen Dateideskriptoren dup() oder das gesamte hidl_handle-Objekt kopieren.

Speicher

Der HIDL-Typ memory wird der hidl_memory-Klasse in libhidlbase zugeordnet, die nicht zugeordneten gemeinsam genutzten Speicher darstellt. Dieses Objekt muss zwischen Prozessen übergeben werden, um den Speicher in HIDL freizugeben. So verwenden Sie den gemeinsamen Arbeitsspeicher:

  1. Rufen Sie eine Instanz von IAllocator ab (derzeit ist nur die Instanz „ashmem“ verfügbar) und verwenden Sie sie, um gemeinsamen Arbeitsspeicher zuzuweisen.
  2. IAllocator::allocate() gibt ein hidl_memory-Objekt zurück, das über HIDL RPC übergeben und mit der mapMemory-Funktion von libhidlmemory einem Prozess zugeordnet werden kann.
  3. mapMemory gibt eine Referenz auf ein sp<IMemory>-Objekt zurück, mit dem auf den Arbeitsspeicher zugegriffen werden kann. (IMemory und IAllocator sind in android.hidl.memory@1.0 definiert.)

Eine Instanz von IAllocator kann zum Zuweisen von Arbeitsspeicher verwendet werden:

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

Tatsächliche Änderungen am Arbeitsspeicher müssen über ein IMemory-Objekt erfolgen, entweder auf der Seite, auf der mem erstellt wurde, oder auf der Seite, die es über HIDL RPC empfängt.

// 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

Schnittstellen können als Objekte übergeben werden. Das Wort interface kann als syntaktischer Zucker für den Typ android.hidl.base@1.0::IBase verwendet werden. Außerdem werden die aktuelle Schnittstelle und alle importierten Schnittstellen als Typ definiert.

Variablen, die Schnittstellen enthalten, sollten starke Zeiger sein: sp<IName>. HIDL-Funktionen, die Schnittstellenparameter annehmen, wandeln Rohzeiger in starke Zeiger um, was zu einem nicht intuitiven Verhalten führt (der Zeiger kann unerwartet gelöscht werden). Speichern Sie HIDL-Schnittstellen immer als sp<>, um Probleme zu vermeiden.