Typy danych

W tej sekcji opisano typy danych HIDL. Szczegółowe informacje o wdrożeniu znajdziesz w dokumentacji HIDL C++ (w przypadku implementacji C++) lub HIDL Java (w przypadku implementacji Java).

Podobieństwa do C++:

  • structs używać składni C++; unions obsługiwać składnię C++ domyślnie. Obie muszą być nazwane; anonimowe struktury i zbiory nie są obsługiwane.
  • W HIDL dozwolone są typy definicji (podobnie jak w C++).
  • Komentarze w stylu C++ są dozwolone i są kopiowane do wygenerowanego pliku nagłówka.

Podobieństwa do Javy:

  • W przypadku 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

Obiekt struct lub union złożony z standardowego układu (podzbiór typów danych plain-old) ma spójny układ pamięci w wygenerowanym kodzie C++, który jest wymuszony za pomocą atrybutów wyrównania w przypadku elementów structunion.

Pierwotne typy HIDL, a także typy enumbitfield (które zawsze pochodzą od typów pierwotnych), mapują się na standardowe typy C++, takie jak std::uint32_tcstdint.

Ponieważ Java nie obsługuje typów bez znaku, typy HIDL bez znaku są mapowane na odpowiadający typ Java z znakiem. Zbiory są mapowane na klasy Java, tablice na tablice Java, a zbiory nie są obecnie obsługiwane w Javie. Ciągi znaków są przechowywane wewnętrznie w formacie UTF-8. Ponieważ Java obsługuje tylko ciągi znaków UTF-16, wartości ciągów wysyłane do implementacji Javy lub z niej są tłumaczone i mogą nie być identyczne po ponownym przetłumaczeniu, ponieważ zestawy znaków nie zawsze są prawidłowo mapowane.

Dane otrzymywane przez interfejs IPC w C++ są oznaczone jako const i znajdują się w pamięci tylko do odczytu, która jest zachowana tylko przez czas trwania wywołania funkcji. Dane otrzymane przez interfejs IPC w języku Java 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 typów można dodawać adnotacje w stylu Java. Adnotacje są parsowane przez program Vendor Test Suite (VTS) w kompilatorze HIDL, ale żadna z takich parsowanych adnotacji nie jest zrozumiana przez kompilator HIDL. Zamiast tego zanalizowane 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 Javie. Do tego samego elementu można dołączyć wiele adnotacji o tej samej nazwie.

Przekierowywanie deklaracji

W HIDL struktury nie mogą być deklarowane z wyprzedzeniem, co uniemożliwia zdefiniowanie przez użytkownika referencyjnych typów danych (np. w HIDL nie można opisać połączonej listy ani drzewa). Większość istniejących interfejsów HAL (przed Androidem 8.x) korzysta z ograniczonej liczby deklaracji w przód, które można usunąć, zmieniając kolejność deklaracji struktury danych.

To ograniczenie umożliwia kopiowanie struktur danych według wartości za pomocą prostego kopiowania głębokiego zamiast śledzenia wartości wskaźników, które mogą występować wielokrotnie w strukturze danych odwołującej się do siebie. Jeśli te same dane są przekazywane 2 razy, na przykład w przypadku 2 parametrów metody lub 2 elementów vec<T> wskazujących na te same dane, tworzone są i przesyłane 2 osobne kopie.

Deklaracje zagnieżdżone

HIDL obsługuje zagnieżdżone deklaracje na dowolnej liczbie poziomów (z jednym wyjątkiem opisanym poniżej). 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 interfejsu mogą być umieszczane tylko w vec<T> i tylko na jednym poziomie (bez vec<vec<IFoo>>).

Składnia wskaźnika surowego

Język HIDL nie używa operatora * i nie obsługuje pełnej elastyczności wskazników bezwzględnych w C/C++. Szczegółowe informacje o tym, jak HIDL otacza wskaźniki i tablice/wektory, znajdziesz w artykule Szablon vec<T>.

Interfejsy

Słowo kluczowe interface ma 2 zastosowania.

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

Na przykład IServiceManager ma taką metodę:

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

Metoda ma wyszukiwać jakiś interfejs według nazwy. Zastąpienie interfejsu android.hidl.base@1.0::IBase jest też identyczne.

Interfejsy mogą być przekazywane tylko na 2 sposoby: jako parametry najwyższego poziomu lub jako elementy obiektu vec<IMyInterface>. Nie mogą być elementami zagłębionych wektorów, struktur, tablic ani unionów.

MQDescriptorSync i MQDescriptorUnsync

Typy MQDescriptorSyncMQDescriptorUnsync przekazują zsynchronizowane lub niezsynchronizowane kolejki szybkich wiadomości (FMQ) za pomocą interfejsu HIDL. Szczegółowe informacje znajdziesz w HIDL C++ (w języku Java nie są obsługiwane kolejki wiadomości).

typ pamięci,

Typ memory służy do reprezentowania niezmapowanej pamięci współdzielonej w HIDL. Jest obsługiwana tylko w C++. Wartość tego typu może być używana po stronie odbierającej, aby zainicjować obiekt IMemory, mapując pamięć i czyniąc ją użyteczną. Więcej informacji znajdziesz w artykule HIDL C++.

Ostrzeżenie: uporządkowane dane umieszczane w wspólnej pamięci MUSZĄ być typu, którego format nigdy się nie zmienia przez cały czas istnienia wersji interfejsu, która przekazuje memory. W przeciwnym razie mogą wystąpić poważne problemy ze zgodnością.

typ wskaźnika,

Typ pointer jest przeznaczony tylko do użytku wewnętrznego w HIDL.

Szablon typu bitfield<T>

bitfield<T>, gdzie T jest enumeracją zdefiniowaną przez użytkownika, sugeruje, że wartość jest OR bitowy wartości zdefiniowanych w T. W wygenerowanym kodzie bitfield<T> występuje jako typ bazowy typu T. 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 Flagi tak samo jak uint8_t.

Dlaczego nie używać funkcji (u)int8_t/(u)int16_t/(u)int32_t/(u)int64_t? Użycie funkcji bitfield zapewnia czytelnikowi dodatkowe informacje HAL, który wie teraz, że setFlags przyjmuje wartość flagi OR bitowej (czyli wie, że wywołanie funkcji setFlags z wartością 16 jest nieprawidłowe). Bezbitfield te informacje są przekazywane tylko za pomocą dokumentacji. Ponadto VTS może sprawdzić, czy wartość flag jest OR bitowy flagi.

uchwyty typu prymitywnego;

OSTRZEŻENIE: adresy wszelkiego rodzaju (nawet adresy fizyczne urządzeń) nie mogą być częścią natywnej nazwy użytkownika. Przekazywanie tych informacji między procesami jest niebezpieczne i czyni je podatnymi na ataki. Wartości przekazywane między procesami muszą zostać zweryfikowane, zanim zostaną użyte do wyszukiwania przydzielonej pamięci w ramach procesu. W przeciwnym razie nieprawidłowe uchwyty mogą powodować nieprawidłowy dostęp do pamięci lub jej uszkodzenie.

Semantyka HIDL to semantyka kopiowania według wartości, co oznacza, że parametry są kopiowane. Duże ilości danych lub dane, które muszą być udostępniane między procesami (np. bariery synchronizacji), są obsługiwane przez przekazywanie opisów plików wskazujących na trwałe obiekty: ashmem dla współdzielonej pamięci, rzeczywistych plików lub czegokolwiek innego, co może być ukryte za opisem pliku. Sterownik bindera powiela deskryptor pliku w ramach innego procesu.

native_handle_t

Android obsługuje native_handle_t, czyli 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;

Natywne uchwyty to zbiór typów int i deskryptorów plików, który jest przekazywany według wartości. Jeden plik może być przechowywany w rodzajowym uchwycie z 0 intami i jednym deskryptorem pliku. Przekazywanie uchwytów za pomocą uchwytów natywnych zakapsułowanych za pomocą typu prymitywnego handle zapewnia, że uchwyty natywne są bezpośrednio uwzględniane w HIDL.

Funkcja native_handle_t ma zmienny rozmiar, więc nie można jej bezpośrednio umieścić w strukturze. Pole uchwytu generuje wskaźnik do osobno przydzielonego native_handle_t.

We wcześniejszych wersjach Androida uchwyty natywnych były tworzone za pomocą tych samych funkcji, które są dostępne w libcutils. W Androidzie 8.0 i nowszych te funkcje są kopiowane do przestrzeni nazw android::hardware::hidl lub przenoszone do NDK. Kod generowany automatycznie przez HIDL serializuje i deserializuje te funkcje automatycznie, bez udziału kodu napisanego przez użytkownika.

Obsługa i własność wskaźnika pliku

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

  • Wywołujący, który przekazuje obiekt hidl_handle jako argument, zachowuje własność deskryptorów plików zawartych w opakowaniu native_handle_t; wywołujący musi zamknąć te deskryptory plików, gdy ich już nie potrzebuje.
  • Proces zwracający obiekt hidl_handle (przekazując go do funkcji _cb) zachowuje własność deskryptorów plików zawartych w native_handle_t ujętym w obiekcie; proces musi zamknąć te deskryptory plików, gdy skończy z nimi pracę.
  • Transport, który otrzymuje hidl_handle, ma prawo własności do deskryptorów plików w native_handle_topakowanych przez obiekt. Odbiorca może używać tych deskryptorów plików w trakcie wywołania zwrotnego transakcji, ale musi sklonować uchwyt natywny, aby używać deskryptorów plików poza wywołaniem zwrotnym. Gdy transakcja zostanie zakończona, transport automatycznie wywołuje funkcję close(), aby uzyskać opisy plików.

HIDL nie obsługuje uchwytów w języku Java (ponieważ Java w ogóle ich nie obsługuje).

Tablice o określonym rozmiarze

W przypadku tablic o określonym rozmiarze w strukturach HIDL ich elementy mogą mieć dowolny typ, a struktura może zawierać:

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

Strings

Ciągi znaków wyglądają inaczej w C++ i w Javie, ale podstawowy typ danych transportowych jest strukturą w C++. Więcej informacji znajdziesz w artykule Typy danych HIDL w C++ lub Typy danych HIDL w Java.

Uwaga: przekazywanie ciągu znaków do Javy lub z niej przez interfejs HIDL (w tym z Java do Javy) powoduje konwersje zestawów znaków, które mogą nie zachować oryginalnego kodowania.

Szablon typu vec<T>

Szablon vec<T> reprezentuje bufor o zmiennej wielkości zawierający instancje T.

T może być:

  • Typy proste (np. uint32_t)
  • Strings
  • Wykazy zdefiniowane przez użytkownika
  • Struktury definiowane przez użytkownika
  • interfejsy lub słowo kluczowe interface (vec<IFoo>, vec<interface> są obsługiwane tylko jako parametry najwyższego poziomu);
  • Nicki
  • bitfield<U>
  • vec<U>, gdzie U znajduje się na tej liście z wyjątkiem interfejsu (np.vec<vec<IFoo>> nie jest obsługiwany).
  • U[] (tabuła o rozmiarach U), gdzie U znajduje się na tej liście z wyjątkiem interfejsu.

Typy zdefiniowane przez użytkownika

W tej sekcji opisujemy typy definiowane przez użytkownika.

Wyliczenie

HIDL nie obsługuje anonimowych typów wyliczeń. W pozostałych przypadkach enumeracje w HIDL są podobne do C++11:

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

Podstawowe wyliczenie jest zdefiniowane w jednym z typów liczb całkowitych w HIDL. Jeśli nie określono wartości dla pierwszego enumeratora typu enum opartego na typie liczby całkowitej, wartość domyślna to 0. Jeśli nie podasz wartości dla późniejszego licznika, wartość domyślna to poprzednia wartość plus 1. Przykład:

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

Typ wyliczeniowy może też dziedziczyć z wcześniej zdefiniowanego typu wyliczeniowego. Jeśli nie określono wartości dla pierwszego enumeratora podrzędnego (w tym przypadku FullSpectrumColor), domyślnie przyjmuje się wartość ostatniego enumeratora nadrzędnego z dodaną 1. Przykład:

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

Ostrzeżenie: dziedziczenie enum działa wstecznie w porównaniu z większością innych typów dziedziczenia. Wartości podrzędnej listy wartości nie można używać jako wartości nadrzędnej listy wartości. Dzieje się tak, ponieważ podrzędna enumeracja zawiera więcej wartości niż nadrzędna. Wartość nadrzędna może jednak być bezpiecznie używana jako wartość podrzędna, ponieważ wartości podrzędne są z definicji superzbiorem wartości nadrzędnych. Pamiętaj o tym podczas projektowania interfejsów, ponieważ oznacza to, że typy odwołujące się do enumeracji nadrzędnych nie mogą w późniejszych wersjach interfejsu odwoływać się do enumeracji podrzędnych.

Wartości typów wyliczeniowych są odwoływane za pomocą dwukropka (a nie kropki, jak w przypadku zagnieżdżonych typów). Składnia to Type:VALUE_NAME. Nie musisz podawać typu, jeśli wartość jest używana w tym samym typie enumeracji lub typach podrzędnych. 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 };

Od Androida 10 enumy mają atrybut len, który można stosować w wyrażeniach stałych. MyEnum::len to łączna liczba wpisów w danym zbiorze. Różni się to od łącznej liczby wartości, która może być mniejsza, gdy wartości są powielane.

Dane uporządkowane

HIDL nie obsługuje anonimowych struktur. W pozostałych aspektach struktury w HIDL są bardzo podobne do struktur w C.

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

Podobnie string może być zawarte w struct (powiązane bufory są oddzielne). W wygenerowanym kodzie C++ wystąpienia typu danych HIDL handle są reprezentowane przez wskaźnik do rzeczywistego natywnego uchwytu, ponieważ wystąpienia typu danych są zmiennej długości.

Union

HIDL nie obsługuje anonimowych związków. W innych przypadkach operacje złożenia są podobne do operacji C.

Zbiory nie mogą zawierać typów naprawczych (takich jak wskaźniki, deskryptory plików czy obiekty bindera). Nie wymagają specjalnych pól ani powiązanych typów. Są po prostu kopiowane za pomocą memcpy() lub podobnej funkcji. Unia może nie zawierać bezpośrednio (lub zawierać za pomocą innych struktur danych) niczego, co wymaga ustawienia przesunięć bindera (czyli odwołań do uchwytu lub interfejsu bindera). 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

Zbiory można też zadeklarować wewnątrz struktur. 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
  }