Typy danych

W tej sekcji opisano typy danych HIDL. Aby uzyskać szczegółowe informacje na temat implementacji, zobacz HIDL C++ (dla implementacji C++) lub HIDL Java (dla implementacji Java).

Podobieństwa do C++ obejmują:

  • structs używają składni C++; unions domyślnie obsługują składnię C++. Obydwa muszą być nazwane; anonimowe struktury i związki nie są obsługiwane.
  • Typedefs są dozwolone w HIDL (tak jak w C++).
  • Komentarze w stylu C++ są dozwolone i są kopiowane do wygenerowanego pliku nagłówkowego.

Podobieństwa do Javy obejmują:

  • Dla każdego pliku HIDL definiuje przestrzeń nazw w stylu Java, która musi zaczynać się od android.hardware. . Wygenerowana przestrzeń nazw C++ to ::android::hardware::… .
  • Wszystkie definicje pliku są zawarte w opakowaniu interface w stylu Java.
  • Deklaracje tablic HIDL są zgodne ze stylem Java, a nie C++. Przykład:
    struct Point {
        int32_t x;
        int32_t y;
    };
    Point[3] triangle;   // sized array
    
  • Komentarze są podobne do formatu javadoc.

Reprezentacja danych

struct lub union złożona z układu standardowego (podzbiór wymagań dotyczących zwykłych, starych typów danych) ma spójny układ pamięci w wygenerowanym kodzie C++, wymuszony jawnymi atrybutami wyrównania elementów członkowskich struct i union .

Pierwotne typy HIDL, a także typy enum i bitfield (które zawsze wywodzą się z typów pierwotnych), mapują na standardowe typy C++, takie jak std::uint32_t z cstdint .

Ponieważ Java nie obsługuje typów bez znaku, typy HIDL bez znaku są mapowane na odpowiedni typ Java ze znakiem. Struktury mapują się na klasy Java; tablice mapują się na tablice Java; unie nie są obecnie obsługiwane w Javie. Ciągi znaków są przechowywane wewnętrznie w formacie UTF8. Ponieważ Java obsługuje tylko ciągi znaków w formacie UTF16, wartości ciągów wysyłane do lub z implementacji Java są tłumaczone i mogą nie być identyczne przy ponownym tłumaczeniu, ponieważ zestawy znaków nie zawsze są odwzorowywane płynnie.

Dane otrzymane przez IPC w C++ są oznaczone jako const i znajdują się w pamięci tylko do odczytu, która utrzymuje się tylko przez czas wywołania funkcji. Dane otrzymane przez IPC w Javie zostały już skopiowane do obiektów Java, więc można je zachować bez dodatkowego kopiowania (i można je modyfikować).

Adnotacje

Do deklaracji typu można dodać adnotacje w stylu Java. Adnotacje są analizowane przez zaplecze Vendor Test Suite (VTS) kompilatora HIDL, ale żadna z takich analizowanych adnotacji nie jest w rzeczywistości rozumiana przez kompilator HIDL. Zamiast tego przeanalizowane adnotacje VTS są obsługiwane przez kompilator VTS (VTSC).

Adnotacje używają składni Java: @annotation lub @annotation(value) lub @annotation(id=value, id=value…) , gdzie wartość może być wyrażeniem stałym, ciągiem znaków lub listą wartości wewnątrz {} , tak jak w Jawa. Do tego samego elementu można dołączyć wiele adnotacji o tej samej nazwie.

Przekaż deklaracje

W języku HIDL struktury nie mogą być deklarowane w przód, co uniemożliwia definiowanie przez użytkownika typów danych samoodwołujących się do siebie (na przykład nie można opisać połączonej listy lub drzewa w języku HIDL). Większość istniejących (starszych niż Android 8.x) warstw HAL ma ograniczone zastosowanie deklaracji forward, które można usunąć, zmieniając kolejność deklaracji struktury danych.

To ograniczenie umożliwia kopiowanie struktur danych według wartości za pomocą prostej głębokiej kopii, zamiast śledzić wartości wskaźników, które mogą występować wiele razy w samoodwołującej się strukturze danych. Jeśli te same dane zostaną przekazane dwukrotnie, na przykład z dwoma parametrami metod lub vec<T> wskazującymi te same dane, tworzone i dostarczane są dwie oddzielne kopie.

Zagnieżdżone deklaracje

HIDL obsługuje zagnieżdżone deklaracje na dowolnej liczbie poziomów (z jednym wyjątkiem opisanym poniżej). Na przykład:

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
    }
    …

Wyjątkiem jest to, że typy interfejsów mogą być osadzone tylko w vec<T> i tylko na jednym poziomie (bez vec<vec<IFoo>> ).

Surowa składnia wskaźnika

Język HIDL nie używa * i nie obsługuje pełnej elastyczności surowych wskaźników C/C++. Aby uzyskać szczegółowe informacje na temat sposobu, w jaki HIDL hermetyzuje wskaźniki i tablice/wektory, zobacz szablon vec<T> .

Interfejsy

Słowo kluczowe interface ma dwa zastosowania.

  • Otwiera definicję interfejsu w pliku .hal.
  • Może być używany jako typ specjalny w polach struktury/unii, parametrach metod i zwracanych wartościach. Jest postrzegany jako ogólny interfejs i synonim android.hidl.base@1.0::IBase .

Na przykład IServiceManager ma następującą metodę:

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

Metoda obiecuje wyszukać jakiś interfejs według nazwy. Podobnie jest z zastąpieniem interfejsu android.hidl.base@1.0::IBase .

Interfejsy można przekazywać tylko na dwa sposoby: jako parametry najwyższego poziomu lub jako elementy członkowskie vec<IMyInterface> . Nie mogą być członkami zagnieżdżonych obiektów vec, struktur, tablic ani unii.

MQDescriptorSync i MQDescriptorUnsync

Typy MQDescriptorSync i MQDescriptorUnsync przekazują zsynchronizowane lub niezsynchronizowane deskryptory szybkiej kolejki komunikatów (FMQ) przez interfejs HIDL. Aby uzyskać szczegółowe informacje, zobacz HIDL C++ (FMQ nie są obsługiwane w Javie).

typ pamięci

Typ memory jest używany do reprezentowania niezamapowanej pamięci współdzielonej w formacie HIDL. Jest obsługiwany tylko w języku C++. Wartość tego typu może zostać użyta po stronie odbiorczej do zainicjowania obiektu IMemory , mapowania pamięci i uczynienia jej użyteczną. Aby uzyskać szczegółowe informacje, zobacz HIDL C++ .

Ostrzeżenie: Dane strukturalne umieszczone w pamięci współdzielonej MUSZĄ być typu, którego format nigdy się nie zmieni przez cały okres istnienia wersji interfejsu przechodzącej przez memory . W przeciwnym razie warstwy HAL mogą mieć poważne problemy ze zgodnością.

typ wskaźnika

Typ pointer jest przeznaczony wyłącznie do użytku wewnętrznego HIDL.

szablon typu bitfield<T>

bitfield<T> w którym T jest wyliczeniem zdefiniowanym przez użytkownika, sugeruje, że wartość jest bitowym OR wartości wyliczeniowych zdefiniowanych w T . W wygenerowanym kodzie bitfield<T> pojawia się jako podstawowy typ T. Na przykład:

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

Kompilator obsługuje typ Flags tak samo jak uint8_t .

Dlaczego nie użyć (u)int8_t / (u)int16_t / (u)int32_t / (u)int64_t ? Użycie bitfield dostarcza czytelnikowi dodatkowych informacji HAL, który teraz wie, że setFlags przyjmuje bitową wartość OR flagi (tzn. wie, że wywołanie setFlags z wartością 16 jest nieprawidłowe). Bez bitfield informacja ta jest przekazywana wyłącznie poprzez dokumentację. Ponadto VTS może faktycznie sprawdzić, czy wartość flag jest bitowym OR flagi.

obsługiwać typ pierwotny

OSTRZEŻENIE: Adresy dowolnego rodzaju (nawet adresy urządzeń fizycznych) nigdy nie mogą być częścią natywnego uchwytu. Przekazywanie tych informacji pomiędzy procesami jest niebezpieczne i naraża je na ataki. Wszelkie wartości przekazywane pomiędzy procesami muszą zostać sprawdzone, zanim zostaną użyte do wyszukania pamięci przydzielonej w procesie. W przeciwnym razie złe uchwyty mogą powodować zły dostęp do pamięci lub uszkodzenie pamięci.

Semantyka HIDL polega na kopiowaniu po wartości, co oznacza, że ​​parametry są kopiowane. Wszelkie duże fragmenty danych lub dane, które muszą być współdzielone między procesami (takie jak płot synchronizacji), są obsługiwane poprzez przekazywanie deskryptorów plików wskazujących trwałe obiekty: ashmem dla pamięci współdzielonej, rzeczywiste pliki lub cokolwiek innego, co może się za nimi ukryć deskryptor pliku. Sterownik spoiwa duplikuje deskryptor pliku do innego procesu.

natywny_uchwyt_t

Android obsługuje native_handle_t , ogólną koncepcję uchwytu zdefiniowaną w libcutils .

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;

Natywny uchwyt to zbiór int i deskryptorów plików przekazywanych przez wartość. Pojedynczy deskryptor pliku może być przechowywany w natywnym uchwycie bez wartości typu int i z pojedynczym deskryptorem pliku. Przekazywanie uchwytów przy użyciu uchwytów natywnych hermetyzowanych typem pierwotnym handle gwarantuje, że uchwyty natywne zostaną bezpośrednio uwzględnione w języku HIDL.

Ponieważ native_handle_t ma zmienny rozmiar, nie można go uwzględnić bezpośrednio w strukturze. Pole uchwytu generuje wskaźnik do oddzielnie przydzielonego native_handle_t .

We wcześniejszych wersjach Androida natywne uchwyty były tworzone przy użyciu tych samych funkcji, które znajdują się w libcutils . W systemie Android 8.0 i nowszych funkcjach te są teraz kopiowane do przestrzeni nazw android::hardware::hidl lub przenoszone do NDK. Automatycznie wygenerowany kod HIDL serializuje i deserializuje te funkcje automatycznie, bez udziału kodu napisanego przez użytkownika.

Własność uchwytu i deskryptora pliku

Kiedy wywołujesz metodę interfejsu HIDL, która przekazuje (lub zwraca) obiekt hidl_handle (najwyższego poziomu lub część typu złożonego), własność zawartych w nim deskryptorów plików jest następująca:

  • Osoba wywołująca przekazująca obiekt hidl_handle jako argument zachowuje własność deskryptorów plików zawartych w opakowanym obiekcie native_handle_t ; osoba wywołująca musi zamknąć te deskryptory plików, gdy skończy z nimi.
  • Proces zwracający obiekt hidl_handle (przekazując go do funkcji _cb ) zachowuje własność deskryptorów plików zawartych w obiekcie native_handle_t zawiniętym w obiekt; proces musi zamknąć te deskryptory plików, gdy skończy z nimi.
  • Transport , który otrzymuje uchwyt hidl_handle , jest właścicielem deskryptorów plików wewnątrz elementu native_handle_t zawiniętego w obiekt; odbiorca może używać tych deskryptorów plików w niezmienionej postaci podczas wywołania zwrotnego transakcji, ale musi sklonować natywny uchwyt, aby móc używać deskryptorów plików poza wywołaniem zwrotnym. Transport automatycznie close() deskryptory plików po zakończeniu transakcji.

HIDL nie obsługuje uchwytów w Javie (ponieważ Java w ogóle nie obsługuje uchwytów).

Tablice rozmiarów

W przypadku tablic o rozmiarach w strukturach HIDL ich elementy mogą być dowolnego typu, jaki może zawierać struktura:

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

Smyczki

Ciągi znaków wyglądają inaczej w C++ i Javie, ale podstawowym typem magazynu transportu jest struktura C++. Aby uzyskać szczegółowe informacje, zobacz Typy danych HIDL C++ lub Typy danych HIDL Java .

Uwaga: Przekazywanie ciągu znaków do lub z języka Java przez interfejs HIDL (w tym język Java do języka Java) spowoduje konwersję zestawu znaków, która może nie zachować dokładnie oryginalnego kodowania.

szablon typu vec<T>

Szablon vec<T> reprezentuje bufor o zmiennym rozmiarze zawierający instancje T .

T może być jednym z następujących:

  • Typy pierwotne (np. uint32_t)
  • Smyczki
  • Wyliczenia zdefiniowane przez użytkownika
  • Struktury zdefiniowane przez użytkownika
  • Interfejsy lub słowo kluczowe interface ( vec<IFoo> , vec<interface> jest obsługiwane tylko jako parametr najwyższego poziomu)
  • Uchwyty
  • pole bitowe<U>
  • vec<U>, gdzie U znajduje się na tej liście, z wyjątkiem interfejsu (np. vec<vec<IFoo>> nie jest obsługiwane)
  • U[] (tablica wielkości U), gdzie U znajduje się na tej liście z wyjątkiem interfejsu

Typy zdefiniowane przez użytkownika

W tej sekcji opisano typy zdefiniowane przez użytkownika.

Wyliczenie

HIDL nie obsługuje anonimowych wyliczeń. W przeciwnym razie wyliczenia w HIDL są podobne do C++ 11:

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

Wyliczenie podstawowe jest zdefiniowane w kategoriach jednego z typów całkowitych w języku HIDL. Jeśli dla pierwszego modułu wyliczającego wyliczenia opartego na typie całkowitym nie określono żadnej wartości, wartość domyślna wynosi 0. Jeśli dla późniejszego modułu wyliczającego nie określono żadnej wartości, wartością domyślną jest poprzednia wartość plus jeden. Na przykład:

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

Wyliczenie może również dziedziczyć po wcześniej zdefiniowanym wyliczeniu. Jeśli nie określono żadnej wartości dla pierwszego modułu wyliczającego wyliczenia podrzędnego (w tym przypadku FullSpectrumColor ), domyślnie jest to wartość ostatniego modułu wyliczającego wyliczenia nadrzędnego plus jeden. Na przykład:

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

Ostrzeżenie: Dziedziczenie Enum działa wstecz niż większość innych typów dziedziczenia. Podrzędnej wartości wyliczeniowej nie można użyć jako nadrzędnej wartości wyliczeniowej. Dzieje się tak, ponieważ wyliczenie podrzędne zawiera więcej wartości niż wyliczenie nadrzędne. Jednakże nadrzędna wartość wyliczeniowa może być bezpiecznie używana jako podrzędna wartość wyliczeniowa, ponieważ podrzędne wartości wyliczeniowe są z definicji nadzbiorem nadrzędnych wartości wyliczeniowych. Należy o tym pamiętać podczas projektowania interfejsów, ponieważ oznacza to, że typy odwołujące się do wyliczeń nadrzędnych nie mogą odwoływać się do wyliczeń podrzędnych w późniejszych iteracjach interfejsu.

Wartości wyliczeń są określane za pomocą składni dwukropków (a nie składni kropek w przypadku typów zagnieżdżonych). Składnia jest Type:VALUE_NAME . Nie ma potrzeby określania typu, jeśli do wartości odwołuje się ten sam typ wyliczeniowy lub typy podrzędne. Przykład:

enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 };
enum Color : Grayscale { RED = WHITE + 1 };
enum Unrelated : uint32_t { FOO = Color:RED + 1 };

Począwszy od Androida 10, wyliczenia mają atrybut len , którego można używać w wyrażeniach stałych. MyEnum::len to całkowita liczba wpisów w tym wyliczeniu. Różni się to od całkowitej liczby wartości, która może być mniejsza w przypadku duplikacji wartości.

Struktura

HIDL nie obsługuje anonimowych struktur. W przeciwnym razie struktury w języku HIDL są bardzo podobne do struktur C.

HIDL nie obsługuje struktur danych o zmiennej długości zawartych w całości w strukturze. Obejmuje to tablicę o nieokreślonej długości, która jest czasami używana jako ostatnie pole struktury w C/C++ (czasami ma rozmiar [0] ). HIDL vec<T> reprezentuje tablice o dynamicznych rozmiarach z danymi przechowywanymi w oddzielnym buforze; takie instancje są reprezentowane przez instancję vec<T> w struct .

Podobnie string może być zawarty w struct (powiązane bufory są oddzielne). W wygenerowanym języku C++ instancje typu uchwytu HIDL są reprezentowane poprzez wskaźnik do rzeczywistego uchwytu natywnego, ponieważ instancje podstawowego typu danych mają zmienną długość.

Unia

HIDL nie obsługuje związków anonimowych. W przeciwnym razie związki są podobne do C.

Unie nie mogą zawierać typów poprawek (wskaźniki, deskryptory plików, obiekty segregatorów itp.). Nie potrzebują specjalnych pól ani powiązanych typów i są po prostu kopiowane za pomocą memcpy() lub jej odpowiednika. Unia nie może bezpośrednio zawierać (lub zawierać poprzez inne struktury danych) niczego, co wymaga ustawienia przesunięć segregatora (tj. odwołań do uchwytu lub interfejsu segregatora). Na przykład:

union UnionType {
uint32_t a;
//  vec<uint32_t> r;  // Error: can't contain a vec<T>
uint8_t b;1
};
fun8(UnionType info); // Legal

Unie można także deklarować wewnątrz struktur. Na przykład:

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
  }