Datentypen

HIDL-Datendeklarationen generieren Datenstrukturen im C++-Standardlayout. Diese Strukturen können an einer beliebigen Stelle platziert werden, die sich natürlich anfühlt (auf dem Stapel, im Datei- oder globalen Bereich oder auf dem Heap) und können auf die gleiche Weise zusammengesetzt werden. Der Clientcode ruft HIDL-Proxycode auf und übergibt konstante Referenzen und primitive Typen, während der Stub- und Proxycode die Details der Serialisierung verbirgt.

Hinweis: Zu keinem Zeitpunkt ist vom Entwickler geschriebener Code erforderlich, um Datenstrukturen explizit zu serialisieren oder zu deserialisieren.

Die folgende Tabelle ordnet HIDL-Grundelemente C++-Datentypen zu:

HIDL-Typ C++-Typ Kopfzeile/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 detaillierter beschrieben.

Aufzählung

Eine Aufzählung in HIDL wird zu einer Aufzählung in C++. Zum Beispiel:

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

… wird:

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 Enumeration mit ::android::hardware::hidl_enum_range iteriert werden. Dieser Bereich umfasst jeden Enumerator in der Reihenfolge, in der er im HIDL-Quellcode erscheint, beginnend mit der übergeordneten Enumeration bis zum letzten untergeordneten Enumerator. Dieser Code durchläuft beispielsweise WRITE , READ , NONE und COMPARE in dieser Reihenfolge. Gegebener 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 Reverse-Iteratoren und kann in constexpr Kontexten verwendet werden. Wenn ein Wert in einer Aufzählung mehrmals vorkommt, erscheint der Wert auch im Bereich mehrmals.

bitfield<T>

bitfield<T> (wobei T eine benutzerdefinierte Enumeration ist) wird zum zugrunde liegenden Typ dieser Enumeration in C++. Im obigen Beispiel wird bitfield<Mode> zu uint8_t .

vec<T>

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

Zusätzlich zum korrekten Ausgeben/Einfügen der Struktur im generierten C++-Header generiert die Verwendung von vec<T> einige praktische Funktionen zum Übersetzen in/von std::vector und Bare- T Zeigern. Wenn vec<T> als Parameter verwendet wird, wird die Funktion, die es verwendet, überladen (es werden zwei Prototypen generiert), um sowohl die HIDL-Struktur als auch einen std::vector<T> -Typ für diesen Parameter zu akzeptieren und zu übergeben.

Array

Konstante Arrays in hidl werden durch die Klasse hidl_array in libhidlbase dargestellt. Ein hidl_array<T, S1, S2, …, SN> stellt ein N-dimensionales Array fester Größe T[S1][S2]…[SN] dar.

Zeichenfolge

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

hidl_string weiß, wie man mithilfe von operator= , impliziten Umwandlungen und der Funktion .c_str() in und aus std::string and char* Zeichenfolge im C-Stil) konvertiert. HIDL-String-Strukturen verfügen über die entsprechenden Kopierkonstruktoren und Zuweisungsoperatoren, um:

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

Darüber hinaus verfügen HIDL-Strings über Konvertierungskonstruktoren, sodass C-Strings ( char * ) und C++-Strings ( std::string ) für Methoden verwendet werden können, die einen HIDL-String akzeptieren.

Struktur

Eine struct in HIDL kann nur Datentypen fester Größe und keine Funktionen enthalten. HIDL-Strukturdefinitionen werden direkt auf Standardlayout- struct in C++ abgebildet, wodurch sichergestellt wird, dass struct ein konsistentes Speicherlayout haben. Eine Struktur kann HIDL-Typen enthalten, einschließlich handle , string und vec<T> , die auf separate Puffer variabler Länge verweisen.

handhaben

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 Nachschlagen des zugewiesenen Speichers innerhalb eines Prozesses verwendet werden. Andernfalls können fehlerhafte Handles zu fehlerhaftem Speicherzugriff oder Speicherbeschädigung führen.

Der handle Typ wird in C++ durch die hidl_handle Struktur dargestellt, die ein einfacher Wrapper um einen Zeiger auf ein const native_handle_t Objekt ist (dies ist in Android schon seit langem 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 den Besitz des native_handle_t -Zeigers, den es umschließt. Es dient lediglich dazu, einen Zeiger auf einen native_handle_t sicher zu speichern, sodass er sowohl in 32- als auch in 64-Bit-Prozessen verwendet werden kann.

Zu den Szenarios, in denen hidl_handle Eigentümer der beigefügten Dateideskriptoren ist, gehören:

  • Nach einem Aufruf der setTo(native_handle_t* handle, bool shouldOwn) -Methode, wobei 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 in/von native_handle_t* -Objekten. Der Hauptzweck des handle Typs in HIDL besteht darin, Dateideskriptoren über HIDL-Schnittstellen zu übergeben. Ein einzelner Dateideskriptor wird daher durch ein native_handle_t ohne int s und ein einzelnes fd dargestellt. Wenn sich Client und Server in einem unterschiedlichen Prozess befinden, kümmert sich die RPC-Implementierung automatisch um den Dateideskriptor, um sicherzustellen, dass beide Prozesse mit derselben Datei arbeiten können.

Obwohl ein von einem Prozess in einem hidl_handle empfangener Dateideskriptor in diesem Prozess gültig ist, bleibt er nicht über die empfangende Funktion hinaus bestehen (er wird geschlossen, sobald die Funktion zurückkehrt). Ein Prozess, der den dauerhaften Zugriff auf den Dateideskriptor beibehalten möchte, muss die eingeschlossenen Dateideskriptoren dup() oder das gesamte hidl_handle Objekt kopieren.

Erinnerung

Der HIDL- memory ist der Klasse hidl_memory in libhidlbase zugeordnet, die nicht zugeordneten gemeinsam genutzten Speicher darstellt. Dies ist das Objekt, das zwischen Prozessen übergeben werden muss, um den Speicher in HIDL gemeinsam zu nutzen. So verwenden Sie den gemeinsamen Speicher:

  1. Besorgen Sie sich eine Instanz von IAllocator (derzeit ist nur die Instanz „ashmem“ verfügbar) und verwenden Sie sie, um gemeinsam genutzten Speicher zuzuweisen.
  2. IAllocator::allocate() gibt ein hidl_memory Objekt zurück, das über HIDL RPC übergeben und mithilfe der mapMemory Funktion von libhidlmemory einem Prozess zugeordnet werden kann.
  3. mapMemory gibt einen Verweis auf ein sp<IMemory> -Objekt zurück, das für den Zugriff auf den Speicher verwendet werden kann. ( IMemory und IAllocator sind in android.hidl.memory@1.0 definiert.)

Eine Instanz von IAllocator kann zum Zuweisen von Speicher 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 Speicher müssen über ein IMemory Objekt erfolgen, entweder auf der Seite, die mem erstellt hat, 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();

Schnittstelle

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; Darüber hinaus 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 übernehmen, konvertieren Rohzeiger in starke Zeiger, was zu nicht intuitivem Verhalten führt (der Zeiger kann unerwartet gelöscht werden). Um Probleme zu vermeiden, speichern Sie HIDL-Schnittstellen immer als sp<> .