Typy danych

Deklaracje danych HIDL generują struktury danych o standardowym układzie C++. Struktury te można umieścić w dowolnym miejscu, które wydaje się naturalne (na stosie, w pliku lub w zasięgu globalnym lub na stercie) i można je komponować w ten sam sposób. Kod klienta wywołuje kod proxy HIDL przekazując stałe odniesienia i typy pierwotne, podczas gdy kod pośredniczący i proxy ukrywa szczegóły serializacji.

Uwaga: W żadnym momencie kod napisany przez programistę nie jest wymagany do jawnej serializacji lub deserializacji struktur danych.

Poniższa tabela mapuje prymitywy HIDL na typy danych C++:

Typ HIDL Typ C++ Nagłówek/biblioteka
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

W poniższych sekcjach opisano bardziej szczegółowo typy danych.

wyliczenie

Wyliczenie w HIDL staje się wyliczeniem w C++. Na przykład:

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

… staje się:

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

Począwszy od Androida 10, wyliczenie można iterować za pomocą ::android::hardware::hidl_enum_range . Ten zakres obejmuje każdy moduł wyliczający w kolejności, w jakiej występuje w kodzie źródłowym HIDL, począwszy od wyliczenia nadrzędnego do ostatniego elementu podrzędnego. Na przykład ten kod iteruje po WRITE , READ , NONE i COMPARE w tej kolejności. Biorąc pod uwagę SpecialMode powyżej:

template <typename T>
using hidl_enum_range = ::android::hardware::hidl_enum_range<T>

for (SpecialMode mode : hidl_enum_range<SpecialMode>) {...}

hidl_enum_range implementuje również iteratory odwrotne i może być używane w kontekstach constexpr . Jeśli wartość pojawia się w wyliczeniu wiele razy, wartość ta pojawia się w zakresie wielokrotnie.

pole bitowe<T>

bitfield<T> (gdzie T jest wyliczeniem zdefiniowanym przez użytkownika) staje się podstawowym typem tego wyliczenia w C++. W powyższym przykładzie bitfield<Mode> staje się uint8_t .

vec<T>

Szablon klasy hidl_vec<T> jest częścią libhidlbase i może być używany do przekazywania wektora dowolnego typu HIDL o dowolnym rozmiarze. Porównywalny kontener o stałym rozmiarze to hidl_array . hidl_vec<T> można także zainicjować tak, aby wskazywał zewnętrzny bufor danych typu T , używając funkcji hidl_vec::setToExternal() .

Oprócz odpowiedniego emitowania/wstawiania struktury w wygenerowanym nagłówku C++, użycie vec<T> generuje pewne wygodne funkcje do tłumaczenia na/z wskaźników std::vector i gołych T Jeśli vec<T> zostanie użyte jako parametr, funkcja używająca go zostanie przeciążona (zostaną wygenerowane dwa prototypy), aby zaakceptować i przekazać zarówno strukturę HIDL, jak i typ std::vector<T> dla tego parametru.

szyk

Stałe tablice w hidl są reprezentowane przez klasę hidl_array w libhidlbase . hidl_array<T, S1, S2, …, SN> reprezentuje N-wymiarową tablicę o stałym rozmiarze T[S1][S2]…[SN] .

strunowy

Klasy hidl_string (część libhidlbase ) można używać do przekazywania ciągów znaków przez interfejsy HIDL i jest ona zdefiniowana w /system/libhidl/base/include/hidl/HidlSupport.h . Pierwszą lokalizacją pamięci w klasie jest wskaźnik do jej bufora znaków.

hidl_string wie, jak konwertować do i z std::string and char* (ciąg w stylu C) za pomocą operator= , niejawnych rzutowań i funkcji .c_str() . Struktury łańcuchowe HIDL mają odpowiednie konstruktory kopiujące i operatory przypisania, aby:

  • Załaduj ciąg HIDL z std::string lub ciągu C.
  • Utwórz nowy std::string z ciągu HIDL.

Ponadto ciągi HIDL mają konstruktory konwersji, więc ciągi C ( char * ) i ciągi C++ ( std::string ) mogą być używane w metodach pobierających ciąg HIDL.

struktura

struct w języku HIDL może zawierać tylko typy danych o stałym rozmiarze i nie może zawierać żadnych funkcji. Definicje struktur HIDL są odwzorowywane bezpośrednio na struct o standardowym układzie w C++, zapewniając, że struct te mają spójny układ pamięci. Struktura może zawierać typy HIDL, w tym handle , string i vec<T> , które wskazują oddzielne bufory o zmiennej długości.

uchwyt

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 przed użyciem do sprawdzenia pamięci przydzielonej w procesie. W przeciwnym razie złe uchwyty mogą powodować zły dostęp do pamięci lub uszkodzenie pamięci.

Typ handle jest reprezentowany przez strukturę hidl_handle w C++, która jest prostym opakowaniem wokół wskaźnika do obiektu const native_handle_t (jest to obecne w Androidzie od dłuższego czasu).

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;

Domyślnie hidl_handle nie przejmuje na własność wskaźnika native_handle_t , który zawija. Istnieje jedynie po to, aby bezpiecznie przechowywać wskaźnik do native_handle_t , tak aby można go było używać zarówno w procesach 32-, jak i 64-bitowych.

Scenariusze, w których hidl_handle jest właścicielem załączonych deskryptorów plików, obejmują:

  • Po wywołaniu metody setTo(native_handle_t* handle, bool shouldOwn) z parametrem shouldOwn ustawionym na true
  • Gdy obiekt hidl_handle jest tworzony przez konstrukcję kopiującą z innego obiektu hidl_handle
  • Gdy obiekt hidl_handle jest kopiowany z innego obiektu hidl_handle

hidl_handle zapewnia zarówno niejawne, jak i jawne konwersje do/z obiektów native_handle_t* . Głównym zastosowaniem typu handle w HIDL jest przekazywanie deskryptorów plików przez interfejsy HIDL. Dlatego pojedynczy deskryptor pliku jest reprezentowany przez native_handle_t bez int s i pojedynczy fd . Jeśli klient i serwer działają w innym procesie, implementacja RPC automatycznie zajmie się deskryptorem pliku, aby zapewnić, że oba procesy będą mogły działać na tym samym pliku.

Chociaż deskryptor pliku odebrany przez proces w hidl_handle będzie ważny w tym procesie, nie będzie on trwał poza funkcją odbierającą (zostanie zamknięty po powrocie funkcji). Proces, który chce zachować stały dostęp do deskryptora pliku, musi dup() załączone deskryptory plików lub skopiować cały obiekt hidl_handle .

pamięć

Typ memory HIDL jest odwzorowywany na klasę hidl_memory w libhidlbase , która reprezentuje niezamapowaną pamięć współdzieloną. Jest to obiekt, który musi być przekazywany pomiędzy procesami, aby współdzielić pamięć w języku HIDL. Aby skorzystać z pamięci współdzielonej:

  1. Uzyskaj instancję IAllocator (obecnie dostępna jest tylko instancja „ashmem”) i użyj jej do alokacji pamięci współdzielonej.
  2. IAllocator::allocate() zwraca obiekt hidl_memory , który można przekazać przez HIDL RPC i odwzorować na proces przy użyciu funkcji mapMemory biblioteki libhidlmemory .
  3. mapMemory zwraca odwołanie do obiektu sp<IMemory> , którego można użyć w celu uzyskania dostępu do pamięci. ( IMemory i IAllocator są zdefiniowane w android.hidl.memory@1.0 .)

Do alokacji pamięci można użyć instancji IAllocator :

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

Rzeczywiste zmiany w pamięci muszą być dokonywane poprzez obiekt IMemory , albo po stronie, która utworzyła mem , albo po stronie, która otrzymuje go przez HIDL RPC.

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

interfejs

Interfejsy można przekazywać jako obiekty. Słowo interfejs może być użyte jako cukier syntaktyczny dla typu android.hidl.base@1.0::IBase ; ponadto bieżący interfejs i wszelkie zaimportowane interfejsy zostaną zdefiniowane jako typ.

Zmienne przechowujące interfejsy powinny być mocnymi wskaźnikami: sp<IName> . Funkcje HIDL pobierające parametry interfejsu konwertują surowe wskaźniki na silne wskaźniki, powodując nieintuicyjne zachowanie (wskaźnik może zostać nieoczekiwanie wyczyszczony). Aby uniknąć problemów, zawsze przechowuj interfejsy HIDL jako sp<> .