In diesem Abschnitt werden HIDL-Datentypen beschrieben. Einzelheiten zur Implementierung finden Sie unter HIDL C++ (für C++-Implementierungen) oder HIDL Java (für Java-Implementierungen).
Ähnlichkeiten zu C++:
structs
verwenden die C++-Syntax undunions
unterstützen standardmäßig die C++-Syntax. Beide müssen benannt sein. Anonyme Strukturen und Unionen werden nicht unterstützt.- Typdefinitionen sind in HIDL (wie in C++) zulässig.
- Kommentare im C++-Format sind zulässig und werden in die generierte Headerdatei kopiert.
Ähnlichkeiten zu Java:
- Für jede Datei definiert HIDL einen Java-Namespace, der mit
android.hardware.
beginnen muss. Der generierte C++-Namespace lautet::android::hardware::…
. - Alle Definitionen der Datei sind in einem Java-
interface
-Wrapper enthalten. - HIDL-Arraydeklarationen folgen dem Java-Stil, nicht dem C++-Stil. Beispiel:
struct Point { int32_t x; int32_t y; }; Point[3] triangle; // sized array
- Kommentare ähneln dem Javadoc-Format.
Datendarstellung
Ein struct
oder union
, das aus einem Standard-Layout besteht (eine Teilmenge der Anforderungen an Plain Old Data Types), hat ein einheitliches Speicherlayout im generierten C++-Code, das durch explizite Ausrichtungsattribute für struct
- und union
-Mitglieder erzwungen wird.
Primitive HIDL-Typen sowie enum
- und bitfield
-Typen (die immer von primitiven Typen abgeleitet sind) werden in C++-Standardtypen wie std::uint32_t
aus cstdint abgebildet.
Da Java keine signaturlosen Typen unterstützt, werden signaturlose HIDL-Typen dem entsprechenden signierten Java-Typ zugeordnet. Structs werden Java-Klassen zugeordnet; Arrays werden Java-Arrays zugeordnet; Unions werden derzeit nicht in Java unterstützt. Strings werden intern als UTF-8 gespeichert. Da Java nur UTF-16-Strings unterstützt, werden Stringwerte, die an eine Java-Implementierung oder von einer Java-Implementierung gesendet werden, übersetzt. Bei einer erneuten Übersetzung sind sie möglicherweise nicht identisch, da die Zeichensätze nicht immer reibungslos zugeordnet werden.
Daten, die über IPC in C++ empfangen werden, sind mit const
gekennzeichnet und befinden sich im Lesezugriffsspeicher, der nur für die Dauer des Funktionsaufrufs vorhanden ist. Daten, die über IPC in Java empfangen wurden, wurden bereits in Java-Objekte kopiert und können daher ohne zusätzliches Kopieren beibehalten und geändert werden.
Anmerkungen
Typdeklarationen können Java-Anmerkungen hinzugefügt werden. Anmerkungen werden vom VTS-Backend (Vendor Test Suite) des HIDL-Compilers geparst, aber keine dieser geparsten Anmerkungen wird vom HIDL-Compiler tatsächlich verstanden. Stattdessen werden geparste VTS-Anmerkungen vom VTS-Compiler (VTSC) verarbeitet.
Für Anmerkungen wird die Java-Syntax verwendet: @annotation
, @annotation(value)
oder @annotation(id=value, id=value…)
. Der Wert kann wie in Java ein konstanter Ausdruck, ein String oder eine Liste von Werten sein.{}
Demselben Element können mehrere Anmerkungen mit demselben Namen hinzugefügt werden.
Vorwärtsdeklarationen
In HIDL können Strukturen nicht vordefiniert werden, was benutzerdefinierte, selbstreferentielle Datentypen unmöglich macht. Beispielsweise können Sie in HIDL keine verknüpfte Liste oder einen Baum beschreiben. Die meisten vorhandenen HALs (vor Android 8.x) verwenden Vorwärtsdeklarationen nur eingeschränkt. Sie können entfernt werden, indem die Deklarationen der Datenstruktur neu angeordnet werden.
Durch diese Einschränkung können Datenstrukturen per Wert mit einer einfachen Deep-Copy kopiert werden, anstatt die Verweise zu verfolgen, die in einer selbstreferentiellen Datenstruktur mehrmals vorkommen können. Wenn dieselben Daten zweimal übergeben werden, z. B. mit zwei Methodenparametern oder vec<T>
s, die auf dieselben Daten verweisen, werden zwei separate Kopien erstellt und gesendet.
Verschachtelte Deklarationen
HIDL unterstützt verschachtelte Deklarationen auf beliebig vielen Ebenen (mit einer Ausnahme, die unten aufgeführt ist). Beispiel:
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 } …
Die Ausnahme ist, dass Schnittstellentypen nur in vec<T>
und nur eine Ebene tief eingebettet werden können (keine vec<vec<IFoo>>
).
Syntax für Rohzeiger
In der HIDL-Sprache wird * nicht verwendet und die volle Flexibilität von C/C++-Raw-Pointern wird nicht unterstützt. Weitere Informationen dazu, wie HIDL Verweise und Arrays/Vektoren kapselt, finden Sie unter vec<T>-Vorlage.
Schnittstellen
Das Keyword interface
hat zwei Verwendungsmöglichkeiten.
- Damit wird die Definition einer Schnittstelle in einer HAL-Datei geöffnet.
- Er kann als spezieller Typ in Struktur-/Union-Feldern, Methodenparametern und Rückgabewerten verwendet werden. Sie gilt als allgemeine Schnittstelle und ist ein Synonym für
android.hidl.base@1.0::IBase
.
IServiceManager
hat beispielsweise die folgende Methode:
get(string fqName, string name) generates (interface service);
Die Methode verspricht, eine Schnittstelle anhand ihres Namens abzurufen. Es ist auch möglich, „interface“ durch „android.hidl.base@1.0::IBase
“ zu ersetzen.
Schnittstellen können nur auf zwei Arten übergeben werden: als Parameter der obersten Ebene oder als Mitglieder einer vec<IMyInterface>
. Sie können keine Mitglieder verschachtelter Vektoren, Strukturen, Arrays oder Unions sein.
MQDescriptorSync und MQDescriptorUnsync
Die Typen MQDescriptorSync
und MQDescriptorUnsync
übergeben synchronisierte oder nicht synchronisierte FMQ-Beschreibungen (Fast Message Queue) über eine HIDL-Schnittstelle. Weitere Informationen finden Sie unter HIDL C++. FMQs werden in Java nicht unterstützt.
Speichertyp
Der Typ memory
wird in HIDL für nicht zugeordneten gemeinsam genutzten Speicher verwendet. Er wird nur in C++ unterstützt. Ein Wert dieses Typs kann an der Empfängerseite verwendet werden, um ein IMemory
-Objekt zu initialisieren, den Arbeitsspeicher zuzuordnen und ihn nutzbar zu machen. Weitere Informationen finden Sie unter HIDL C++.
Warnung:Strukturierte Daten, die im gemeinsamen Speicher abgelegt werden, MÜSSEN einen Typ haben, dessen Format sich während der Lebensdauer der Benutzeroberflächenversion, die memory
übergibt, nicht ändert. Andernfalls können bei HALs schwerwiegende Kompatibilitätsprobleme auftreten.
cursor type
Der Typ pointer
ist nur für die interne Verwendung von HIDL vorgesehen.
Vorlage für den Typ „bitfield<T>“
bitfield<T>
, wobei T
eine nutzerdefinierte Aufzählung ist, bedeutet, dass der Wert ein Bitwise-OR der in T
definierten Aufzählungswerte ist. Im generierten Code wird bitfield<T>
als zugrunde liegender Typ von T angezeigt. Beispiel:
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);
Der Compiler behandelt den Typ „Flags“ genauso wie uint8_t
.
Warum nicht (u)int8_t
/(u)int16_t
/(u)int32_t
/(u)int64_t
verwenden?bitfield
liefert dem Leser zusätzliche HAL-Informationen. Er weiß jetzt, dass setFlags
einen Bitwise-OR-Wert des Flags annimmt, d. h., dass der Aufruf von setFlags
mit 16 ungültig ist. Ohne bitfield
werden diese Informationen nur über die Dokumentation übermittelt. Darüber hinaus kann VTS prüfen, ob der Wert der Flags ein Bitwise-OR von Flag ist.
Ziehpunkte für primitive Typen
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.
Die HIDL-Semantik ist „Copy-by-Value“, was bedeutet, dass Parameter kopiert werden.
Große Datenmengen oder Daten, die zwischen Prozessen freigegeben werden müssen (z. B. ein Synchronisierungsgrenzwert), werden durch Weitergabe von Dateideskriptoren verwaltet, die auf persistente Objekte verweisen: ashmem
für gemeinsamen Speicher, tatsächliche Dateien oder alles andere, was sich hinter einem Dateideskriptor verbergen kann. Der Binder-Treiber dupliziert den Dateideskriptor in den anderen Prozess.
native_handle_t
Android unterstützt native_handle_t
, ein allgemeines Handle-Konzept, das in libcutils
definiert ist.
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;
Ein nativer Handle ist eine Sammlung von Ganzzahlen und Dateideskriptoren, die per Wert übergeben werden. Ein einzelner Dateideskriptor kann in einem nativen Handle ohne Ints und einem einzelnen Dateideskriptor gespeichert werden. Wenn Sie Handles mit nativen Handles übergeben, die mit dem primitiven Typ handle
gekapselt sind, werden native Handles direkt in HIDL aufgenommen.
Da ein native_handle_t
eine variable Größe hat, kann es nicht direkt in ein Struct aufgenommen werden. Ein Handle-Feld generiert einen Verweis auf eine separat zugewiesene native_handle_t
.
In früheren Android-Versionen wurden native Handles mit denselben Funktionen in libcutils erstellt.
Unter Android 8.0 und höher werden diese Funktionen jetzt in den Namespace android::hardware::hidl
kopiert oder in das NDK verschoben. Der automatisch generierte HIDL-Code serialisiert und deserialisiert diese Funktionen automatisch, ohne dass von Nutzern geschriebener Code erforderlich ist.
Eigentümerschaft von Handle und Dateideskriptor
Wenn Sie eine HIDL-Schnittstellenmethode aufrufen, die ein hidl_handle
-Objekt (entweder auf oberster Ebene oder Teil eines zusammengesetzten Typs) übergibt (oder zurückgibt), gilt für die Eigentümerschaft der darin enthaltenen Dateideskriptoren Folgendes:
- Der Aufrufer, der ein
hidl_handle
-Objekt als Argument übergibt, behält die Eigentumsrechte an den Dateideskriptoren, die in der umhülltennative_handle_t
enthalten sind. Der Aufrufer muss diese Dateideskriptoren schließen, wenn er sie nicht mehr benötigt. - Der Prozess, der ein
hidl_handle
-Objekt zurückgibt (indem es an eine_cb
-Funktion übergeben wird), behält die Eigentümerschaft der Dateideskriptoren, die in der vom Objekt umhülltennative_handle_t
enthalten sind. Der Prozess muss diese Dateideskriptoren schließen, wenn er sie nicht mehr benötigt. - Ein Transport, der eine
hidl_handle
empfängt, ist Inhaber der Dateideskriptoren innerhalb des vom Objekt verpacktennative_handle_t
. Der Empfänger kann diese Dateideskriptoren während des Transaktions-Callbacks unverändert verwenden, muss aber den nativen Handle klonen, um die Dateideskriptoren über den Callback hinaus zu verwenden. Der Transport ruft nach Abschluss der Transaktion automatischclose()
für die Dateideskriptoren auf.
HIDL unterstützt keine Handles in Java, da Java Handles überhaupt nicht unterstützt.
Arrays mit fester Größe
Die Elemente von Arrays mit fester Größe in HIDL-Structs können jeden Typ haben, den ein Struct enthalten kann:
struct foo { uint32_t[3] x; // array is contained in foo };
Strings
Strings werden in C++ und Java unterschiedlich dargestellt, der zugrunde liegende Transportspeichertyp ist jedoch eine C++-Struktur. Weitere Informationen finden Sie unter HIDL-C++-Datentypen oder HIDL-Java-Datentypen.
Hinweis:Wenn Sie einen String über eine HIDL-Schnittstelle an Java oder von Java übergeben (einschließlich Java-zu-Java), führt dies zu Zeichensatzkonvertierungen, bei denen die ursprüngliche Codierung möglicherweise nicht beibehalten wird.
Vorlage für den Typ „vec<T>“
Die Vorlage vec<T>
stellt einen Puffer mit variabler Größe dar, der Instanzen von T
enthält.
T
kann eines der Folgenden sein:
- Primitive Typen (z.B. uint32_t)
- Strings
- Benutzerdefinierte Enumerationen
- Benutzerdefinierte Strukturen
- Schnittstellen oder das Schlüsselwort
interface
(vec<IFoo>
undvec<interface>
werden nur als Parameter der obersten Ebene unterstützt) - Aliasse
- bitfield<U>
- vec<U>, wobei U in dieser Liste enthalten ist, mit Ausnahme von „interface“ (z.B. wird
vec<vec<IFoo>>
nicht unterstützt) - U[] (begrenztes Array von U), wobei U in dieser Liste außer „interface“ enthalten ist
Benutzerdefinierte Typen
In diesem Abschnitt werden benutzerdefinierte Typen beschrieben.
Enum
HIDL unterstützt keine anonymen Enumerationen. Ansonsten ähneln Enumerationen in HIDL denen in C++11:
enum name : type { enumerator , enumerator = constexpr , … }
Eine Basisenumerierung wird anhand eines der Ganzzahltypen in HIDL definiert. Wenn für den ersten Enumerator eines Enum, das auf einem Ganzzahltyp basiert, kein Wert angegeben ist, wird standardmäßig der Wert 0 verwendet. Wenn für einen späteren Enumerator kein Wert angegeben ist, wird standardmäßig der vorherige Wert plus eins verwendet. Beispiel:
// RED == 0 // BLUE == 4 (GREEN + 1) enum Color : uint32_t { RED, GREEN = 3, BLUE }
Ein Enum kann auch von einem zuvor definierten Enum erben. Wenn für den ersten Enumerator einer untergeordneten Enum-Instanz kein Wert angegeben ist (in diesem Fall FullSpectrumColor
), wird standardmäßig der Wert des letzten Enumerators der übergeordneten Enum-Instanz plus 1 verwendet. Beispiel:
// ULTRAVIOLET == 5 (Color:BLUE + 1) enum FullSpectrumColor : Color { ULTRAVIOLET }
Warnung:Die Enum-Vererbung funktioniert im Gegensatz zu den meisten anderen Vererbungstypen rückwärts. Ein untergeordneter Aufzählungswert kann nicht als übergeordneter Aufzählungswert verwendet werden. Das liegt daran, dass eine untergeordnete Enum-Instanz mehr Werte als die übergeordnete enthält. Ein übergeordnetes Enum-Element kann jedoch problemlos als untergeordnetes Enum-Element verwendet werden, da untergeordnete Enum-Elemente per Definition ein Obermenge der übergeordneten Enum-Elemente sind. Denken Sie daran, wenn Sie Oberflächen entwerfen, da Typen, die sich auf übergeordnete Enumerationen beziehen, sich in späteren Iterationen Ihrer Oberfläche nicht auf untergeordnete Enumerationen beziehen können.
Auf Werte von Enumerationen wird mit der Doppelpunktsyntax verwiesen (nicht mit der Punktsyntax wie bei verschachtelten Typen). Die Syntax lautet Type:VALUE_NAME
. Der Typ muss nicht angegeben werden, wenn auf den Wert im selben Enum-Typ oder in untergeordneten Typen verwiesen wird. Beispiel:
enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 }; enum Color : Grayscale { RED = WHITE + 1 }; enum Unrelated : uint32_t { FOO = Color:RED + 1 };
Ab Android 10 haben Enumerationen das Attribut len
, das in Konstantenausdrücken verwendet werden kann.
MyEnum::len
ist die Gesamtzahl der Einträge in dieser Aufzählung.
Dieser Wert unterscheidet sich von der Gesamtzahl der Werte, die bei doppelten Werten geringer sein kann.
Struct
HIDL unterstützt keine anonymen Strukturen. Ansonsten ähneln Strukturen in HIDL sehr C-Strukturen.
HIDL unterstützt keine Datenstrukturen mit variabler Länge, die vollständig in einem struct enthalten sind. Dazu gehören Arrays mit unbestimmter Länge, die manchmal als letztes Feld eines Structs in C/C++ verwendet werden (manchmal mit einer Größe von [0]
). HIDL vec<T>
steht für Arrays mit dynamischer Größe, deren Daten in einem separaten Puffer gespeichert werden. Solche Instanzen werden in der struct
durch eine Instanz von vec<T>
dargestellt.
Ebenso kann string
in einem struct
enthalten sein (die zugehörigen Puffer sind getrennt). Im generierten C++-Code werden Instanzen des HIDL-Handle-Typs über einen Verweis auf den tatsächlichen nativen Handle dargestellt, da Instanzen des zugrunde liegenden Datentyps eine variable Länge haben.
Union
HIDL unterstützt keine anonymen Unions. Ansonsten ähneln sie C-Unions.
Unionen dürfen keine Korrekturtypen enthalten, z. B. Zeiger, Dateideskriptoren oder Binderobjekte. Sie benötigen keine speziellen Felder oder verknüpften Typen und werden einfach mit memcpy()
oder einem ähnlichen Befehl kopiert. Eine Union darf nichts direkt oder mithilfe anderer Datenstrukturen enthalten, für das Binder-Offset (d. h. Handle- oder Binder-Interface-Referenzen) festgelegt werden muss. Beispiel:
union UnionType { uint32_t a; // vec<uint32_t> r; // Error: can't contain a vec<T> uint8_t b;1 }; fun8(UnionType info); // Legal
Unionen können auch innerhalb von Strukturen deklariert werden. Beispiel:
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 }