Datentypen

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).

Zu den Ähnlichkeiten mit C++ gehören:

  • structs verwenden C++-Syntax; unions unterstützen standardmäßig die C++-Syntax. Beide müssen benannt werden; Anonyme Strukturen und Unions werden nicht unterstützt.
  • Typedefs sind in HIDL zulässig (wie auch in C++).
  • Kommentare im C++-Stil sind zulässig und werden in die generierte Header-Datei kopiert.

Zu den Ähnlichkeiten mit Java gehören:

  • Für jede Datei definiert HIDL einen Namespace im Java-Stil, der mit android.hardware. . Der generierte C++-Namespace ist ::android::hardware::… .
  • Alle Definitionen der Datei sind in einem interface Wrapper im Java-Stil enthalten.
  • HIDL-Array-Deklarationen 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.

Daten Präsentation

Eine aus Standard-Layout (einer Teilmenge der Anforderungen von Plain-Old-Data-Typen) bestehende struct oder union verfügt über ein konsistentes 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 auf Standard-C++-Typen wie std::uint32_t von cstdint abgebildet.

Da Java keine vorzeichenlosen Typen unterstützt, werden vorzeichenlose HIDL-Typen dem entsprechenden signierten Java-Typ zugeordnet. Strukturen werden Java-Klassen zugeordnet. Arrays werden Java-Arrays zugeordnet; Gewerkschaften werden derzeit in Java nicht unterstützt. Zeichenfolgen werden intern als UTF8 gespeichert. Da Java nur UTF16-Zeichenfolgen unterstützt, werden Zeichenfolgenwerte, die an oder von einer Java-Implementierung gesendet werden, übersetzt und sind bei der Neuübersetzung möglicherweise nicht identisch, da die Zeichensätze nicht immer reibungslos zugeordnet werden.

Über IPC in C++ empfangene Daten sind const gekennzeichnet und befinden sich im Nur-Lese-Speicher, der nur für die Dauer des Funktionsaufrufs bestehen bleibt. Über IPC in Java empfangene Daten wurden bereits in Java-Objekte kopiert, sodass sie ohne zusätzliches Kopieren beibehalten (und möglicherweise geändert) werden können.

Anmerkungen

Typdeklarationen können Anmerkungen im Java-Stil hinzugefügt werden. Anmerkungen werden vom Vendor Test Suite (VTS)-Backend des HIDL-Compilers analysiert, aber keine dieser analysierten Anmerkungen wird vom HIDL-Compiler tatsächlich verstanden. Stattdessen werden geparste VTS-Anmerkungen vom VTS-Compiler (VTSC) verarbeitet.

Anmerkungen verwenden die Java-Syntax: @annotation oder @annotation(value) oder @annotation(id=value, id=value…) wobei value entweder ein konstanter Ausdruck, eine Zeichenfolge oder eine Liste von Werten innerhalb von {} sein kann, genau wie in Java. An dasselbe Element können mehrere Anmerkungen mit demselben Namen angehängt werden.

Deklarationen weiterleiten

In HIDL dürfen Strukturen nicht vorwärts deklariert werden, was benutzerdefinierte, selbstreferenzielle Datentypen unmöglich macht (z. B. können Sie in HIDL keine verknüpfte Liste oder einen Baum beschreiben). Die meisten vorhandenen HALs (vor Android 8.x) verwenden nur begrenzt Vorwärtsdeklarationen, die durch Neuanordnen der Datenstrukturdeklarationen entfernt werden können.

Diese Einschränkung ermöglicht das Kopieren von Datenstrukturen nach Wert mit einer einfachen Tiefenkopie, anstatt Zeigerwerte zu verfolgen, die möglicherweise mehrmals in einer selbstreferenziellen Datenstruktur vorkommen. Wenn dieselben Daten zweimal übergeben werden, beispielsweise mit zwei Methodenparametern oder vec<T> s, die auf dieselben Daten verweisen, werden zwei separate Kopien erstellt und übermittelt.

Verschachtelte Deklarationen

HIDL unterstützt verschachtelte Deklarationen auf beliebig vielen Ebenen (mit einer unten aufgeführten Ausnahme). Zum 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 besteht darin, dass Schnittstellentypen nur in vec<T> und nur eine Ebene tief eingebettet werden können (kein vec<vec<IFoo>> ).

Rohzeigersyntax

Die HIDL-Sprache verwendet kein * und unterstützt nicht die volle Flexibilität von C/C++-Rohzeigern. Einzelheiten dazu, wie HIDL Zeiger und Arrays/Vektoren kapselt, finden Sie unter vec<T> template .

Schnittstellen

Das Schlüsselwort interface hat zwei Verwendungszwecke.

  • Es öffnet die Definition einer Schnittstelle in einer .hal-Datei.
  • Es kann als spezieller Typ in Struktur-/Union-Feldern, Methodenparametern und Rückgaben verwendet werden. Es wird als allgemeine Schnittstelle und Synonym für android.hidl.base@1.0::IBase angesehen.

IServiceManager verfügt beispielsweise über die folgende Methode:

get(string fqName, string name) generates (interface service);

Die Methode verspricht, eine Schnittstelle anhand des Namens zu suchen. Es ist auch identisch, die Schnittstelle 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 eines vec<IMyInterface> . Sie können keine Mitglieder verschachtelter VECs, Strukturen, Arrays oder Unions sein.

MQDescriptorSync und MQDescriptorUnsync

Die Typen MQDescriptorSync und MQDescriptorUnsync übergeben synchronisierte oder nicht synchronisierte FMQ-Deskriptoren (Fast Message Queue) über eine HIDL-Schnittstelle. Einzelheiten finden Sie unter HIDL C++ (FMQs werden in Java nicht unterstützt).

Speichertyp

Der memory wird verwendet, um nicht zugeordneten gemeinsam genutzten Speicher in HIDL darzustellen. Es wird nur in C++ unterstützt. Ein Wert dieses Typs kann auf der Empfangsseite verwendet werden, um ein IMemory Objekt zu initialisieren, den Speicher abzubilden und nutzbar zu machen. Einzelheiten finden Sie unter HIDL C++ .

Warnung: Im gemeinsam genutzten Speicher abgelegte strukturierte Daten MÜSSEN von einem Typ sein, dessen Format sich während der Lebensdauer der Schnittstellenversion, die den memory weitergibt, niemals ändert. Andernfalls kann es bei HALs zu schwerwiegenden Kompatibilitätsproblemen kommen.

Zeigertyp

Der pointer ist nur für den internen HIDL-Gebrauch bestimmt.

Vorlage vom Typ bitfield<T>

bitfield<T> , in dem T eine benutzerdefinierte Aufzählung ist, deutet darauf hin, dass der Wert ein bitweises ODER der in T definierten Aufzählungswerte ist. Im generierten Code erscheint bitfield<T> als zugrunde liegender Typ von T. 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? Die Verwendung bitfield liefert dem Leser zusätzliche HAL-Informationen, der nun weiß, dass setFlags einen bitweisen ODER-Wert von Flag annimmt (d. h. weiß, dass der Aufruf setFlags mit 16 ungültig ist). Ohne bitfield werden diese Informationen nur über die Dokumentation übermittelt. Darüber hinaus kann VTS tatsächlich prüfen, ob der Wert von Flags ein bitweises ODER von Flag ist.

Behandeln Sie den primitiven Typ

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.

Bei der HIDL-Semantik handelt es sich um eine Copy-by-Value-Semantik, was bedeutet, dass Parameter kopiert werden. Alle großen Datenmengen oder Daten, die zwischen Prozessen gemeinsam genutzt werden müssen (z. B. ein Synchronisierungszaun), werden durch die Weitergabe von Dateideskriptoren verarbeitet, die auf persistente Objekte verweisen: ashmem für gemeinsam genutzten Speicher, tatsächliche Dateien oder alles andere, was sich dahinter verbergen kann ein Dateideskriptor. 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 natives Handle ist eine Sammlung von Ints und Dateideskriptoren, die als Wert weitergegeben werden. Ein einzelner Dateideskriptor kann in einem nativen Handle ohne Ints und einem einzelnen Dateideskriptor gespeichert werden. Durch die Übergabe von Handles mithilfe nativer Handles, die mit dem handle Primitivtyp gekapselt sind, wird sichergestellt, dass native Handles direkt in HIDL enthalten sind.

Da ein native_handle_t eine variable Größe hat, kann er nicht direkt in eine Struktur eingefügt werden. Ein Handle-Feld generiert einen Zeiger auf einen separat zugewiesenen native_handle_t .

In früheren Android-Versionen wurden native Handles mit denselben Funktionen erstellt, die in libcutils vorhanden sind. In Android 8.0 und höher werden diese Funktionen nun 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 vom Benutzer geschriebener Code beteiligt ist.

Besitzer von Handle und Dateideskriptor

Wenn Sie eine HIDL-Schnittstellenmethode aufrufen, die ein hidl_handle Objekt (entweder auf der obersten Ebene oder als Teil eines zusammengesetzten Typs) übergibt (oder zurückgibt), ist der Besitz der darin enthaltenen Dateideskriptoren wie folgt:

  • Der Aufrufer , der ein hidl_handle Objekt als Argument übergibt, behält den Besitz der Dateideskriptoren, die in dem von ihm umschlossenen native_handle_t enthalten sind. Der Aufrufer muss diese Dateideskriptoren schließen, wenn er mit ihnen fertig ist.
  • Der Prozess , der ein hidl_handle Objekt zurückgibt (indem er es an eine _cb Funktion übergibt), behält den Besitz der Dateideskriptoren, die im vom Objekt umschlossenen native_handle_t enthalten sind. Der Prozess muss diese Dateideskriptoren schließen, wenn er mit ihnen fertig ist.
  • Ein Transport , der ein hidl_handle empfängt, ist Eigentümer der Dateideskriptoren innerhalb des vom Objekt umschlossenen native_handle_t ; Der Empfänger kann diese Dateideskriptoren während des Transaktionsrückrufs unverändert verwenden, muss jedoch das native Handle klonen, um die Dateideskriptoren über den Rückruf hinaus verwenden zu können. Der Transport close() , wenn die Transaktion abgeschlossen ist.

HIDL unterstützt keine Handles in Java (da Java überhaupt keine Handles unterstützt).

Große Arrays

Bei großen Arrays in HIDL-Strukturen können ihre Elemente von jedem Typ sein, den eine Struktur enthalten kann:

struct foo {
uint32_t[3] x; // array is contained in foo
};

Saiten

Zeichenfolgen werden in C++ und Java unterschiedlich angezeigt, der zugrunde liegende Transportspeichertyp ist jedoch eine C++-Struktur. Einzelheiten finden Sie unter HIDL C++-Datentypen oder HIDL Java-Datentypen .

Hinweis: Die Übergabe einer Zeichenfolge an oder von Java über eine HIDL-Schnittstelle (einschließlich Java zu Java) führt zu Zeichensatzkonvertierungen, bei denen die ursprüngliche Codierung möglicherweise nicht genau erhalten bleibt.

Vorlage vom Typ vec<T>

Die Vorlage vec<T> stellt einen Puffer variabler Größe dar, der Instanzen von T enthält.

T kann einer der folgenden sein:

  • Primitive Typen (z. B. uint32_t)
  • Saiten
  • Benutzerdefinierte Aufzählungen
  • Benutzerdefinierte Strukturen
  • Schnittstellen oder das Schlüsselwort interface ( vec<IFoo> , vec<interface> wird nur als Parameter der obersten Ebene unterstützt)
  • Griffe
  • Bitfeld<U>
  • vec<U>, wobei U in dieser Liste mit Ausnahme der Schnittstelle steht (z. B. wird vec<vec<IFoo>> nicht unterstützt)
  • U[] (großes Array von U), wobei U in dieser Liste mit Ausnahme der Schnittstelle enthalten ist

Benutzerdefinierte Typen

In diesem Abschnitt werden benutzerdefinierte Typen beschrieben.

Aufzählung

HIDL unterstützt keine anonymen Enumerationen. Ansonsten ähneln Aufzählungen in HIDL C++11:

enum name : type { enumerator , enumerator = constexpr , …  }

Eine Basisenum wird anhand eines der Integer-Typen in HIDL definiert. Wenn für den ersten Enumerator einer Aufzählung, die auf einem ganzzahligen Typ basiert, kein Wert angegeben wird, ist der Wert standardmäßig 0. Wenn für einen späteren Enumerator kein Wert angegeben ist, wird der Wert standardmäßig auf den vorherigen Wert plus eins gesetzt. Zum Beispiel:

// RED == 0
// BLUE == 4 (GREEN + 1)
enum Color : uint32_t { RED, GREEN = 3, BLUE }

Eine Aufzählung kann auch von einer zuvor definierten Aufzählung erben. Wenn für den ersten Enumerator einer untergeordneten Enumeration (in diesem Fall FullSpectrumColor ) kein Wert angegeben ist, wird standardmäßig der Wert des letzten Enumerators der übergeordneten Enumeration plus eins verwendet. Zum Beispiel:

// ULTRAVIOLET == 5 (Color:BLUE + 1)
enum FullSpectrumColor : Color { ULTRAVIOLET }

Warnung: Die Enum-Vererbung funktioniert umgekehrt wie die meisten anderen Vererbungsarten. Ein untergeordneter Enumerationswert kann nicht als übergeordneter Enumerationswert verwendet werden. Dies liegt daran, dass eine untergeordnete Enumeration mehr Werte enthält als die übergeordnete. Ein übergeordneter Enum-Wert kann jedoch sicher als untergeordneter Enum-Wert verwendet werden, da untergeordnete Enum-Werte per Definition eine Obermenge der übergeordneten Enum-Werte sind. Beachten Sie dies beim Entwerfen von Schnittstellen, da dies bedeutet, dass Typen, die auf übergeordnete Aufzählungen verweisen, in späteren Iterationen Ihrer Schnittstelle nicht auf untergeordnete Aufzählungen verweisen können.

Auf Werte von Aufzählungen wird mit der Doppelpunktsyntax verwiesen (nicht mit der Punktsyntax wie bei verschachtelten Typen). Die Syntax lautet Type:VALUE_NAME . Es ist nicht erforderlich, den Typ anzugeben, wenn auf den Wert im selben Aufzählungstyp oder denselben 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 verfügen Enums über ein len Attribut, das in konstanten Ausdrücken verwendet werden kann. MyEnum::len ist die Gesamtzahl der Einträge in dieser Enumeration. Dies unterscheidet sich von der Gesamtzahl der Werte, die kleiner sein kann, wenn Werte dupliziert werden.

Struktur

HIDL unterstützt keine anonymen Strukturen. Ansonsten sind Strukturen in HIDL C sehr ähnlich.

HIDL unterstützt keine Datenstrukturen variabler Länge, die vollständig in einer Struktur enthalten sind. Dazu gehört das Array mit unbestimmter Länge, das manchmal als letztes Feld einer Struktur in C/C++ verwendet wird (manchmal mit einer Größe von [0] ). HIDL vec<T> stellt Arrays mit dynamischer Größe dar, wobei die Daten in einem separaten Puffer gespeichert sind; Solche Instanzen werden mit einer Instanz von vec<T> in der struct dargestellt.

Ebenso kann string in einer struct enthalten sein (zugehörige Puffer sind separat). Im generierten C++ werden Instanzen des HIDL-Handle-Typs über einen Zeiger auf das tatsächliche native Handle dargestellt, da Instanzen des zugrunde liegenden Datentyps eine variable Länge haben.

Union

HIDL unterstützt keine anonymen Gewerkschaften. Ansonsten ähneln Gewerkschaften C.

Unions können keine Korrekturtypen (Zeiger, Dateideskriptoren, Binderobjekte usw.) enthalten. Sie benötigen keine speziellen Felder oder zugehörigen Typen und werden einfach über memcpy() oder ein Äquivalent kopiert. Eine Union darf nichts direkt (oder über andere Datenstrukturen enthalten) enthalten, was das Festlegen von Binder-Offsets erfordert (z. B. Handle- oder Binder-Interface-Referenzen). Zum 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

Unions können auch innerhalb von Strukturen deklariert werden. Zum 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
  }