Typy danych

Deklaracje danych HIDL generują struktury danych o standardowej architekturze C++. Te struktury można umieścić w dowolnym miejscu, które wydaje się naturalne (na stosie, w pliku, w zakresie globalnym lub na stosie) i można je skomponować w taki sam sposób. Kod klienta wywołuje kod zastępczy HIDL, przekazując stałe odwołania i typy prymitywne, a kod zastępczy i pośredniczący ukrywa szczegóły serializacji.

Uwaga: w żadnym momencie nie jest wymagane jawne serializowanie ani deserializowanie struktur danych za pomocą kodu napisanego przez programistę.

W tabeli poniżej pokazano mapowanie prymitywów 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 znajdziesz bardziej szczegółowe informacje o typach danych.

enum

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

… zmienia się w:

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, można iterować enum za pomocą ::android::hardware::hidl_enum_range. Zakres ten obejmuje wszystkie enumeratory w kolejności, w jakiej występują w źródle kodu HIDL, począwszy od enumeratora nadrzędnego do ostatniego podrzędnego. Na przykład ten kod iteruje w kolejności WRITE, READ, NONECOMPARE. Z poziomu 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 też odwrotne iteracje i może być używany w kontekstach constexpr. Jeśli wartość występuje w enumeracji kilka razy, pojawia się też kilka razy w zakresie.

bitfield<T>

bitfield<T> (gdzie T jest wyliczeniem zdefiniowanym przez użytkownika) staje się typem podstawowym 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 służyć do przekazywania wektora dowolnego typu HIDL o dowolnym rozmiarze. Porównywalny kontener o stałym rozmiarze to hidl_array. Za pomocą funkcji hidl_vec::setToExternal() możesz też zainicjować hidl_vec<T>, aby wskazywało zewnętrzny bufor danych typu T.

Oprócz emitowania/wstawiania struktury w odpowiednim miejscu w wygenerowanym nagłówku C++, użycie vec<T> powoduje wygenerowanie kilku funkcji ułatwiających tłumaczenie na/z std::vector i bezwzględnych wskaźników T. Jeśli vec<T> jest używany jako parametr, funkcja, która go używa, jest przeciążona (generowane są 2 prototypy), aby przyjmować i przekazywać zarówno strukturę HIDL, jak i typ std::vector<T> dla tego parametru.

tablica

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

ciąg znaków

Klasy hidl_string (część klasy libhidlbase) można używać do przekazywania ciągów znaków przez interfejsy HIDL. Klasa ta jest zdefiniowana w pliku /system/libhidl/base/include/hidl/HidlSupport.h. Pierwsze miejsce przechowywania w klasie to wskaźnik do bufora znaków.

hidl_string potrafi konwertować dane z poziomu std::string and char* (ciąg znaków w stylu C) na poziom std::string and char* i odwrotnie za pomocą funkcji operator=, domyślnych konwersji i funkcji .c_str(). Struktury ciągu znaków HIDL mają odpowiednie konstruktory kopii i operatory przypisania do:

  • Wczytaj ciąg tekstowy HIDL z poziomu ciągu tekstowego std::string lub C.
  • Utwórz nową wartość std::string na podstawie ciągu HIDL.

Dodatkowo ciągi znaków HIDL mają konstruktory konwersji, dzięki czemu w metodach, które przyjmują ciąg znaków HIDL, można używać ciągów znaków C (char *) i C++ (std::string).

struct

W HIDL element struct może zawierać tylko typy danych o stałym rozmiarze i żadne funkcje. Definicje struktury HIDL są mapowane bezpośrednio na standardowe struktury struct w C++, co zapewnia spójną strukturę pamięci dla struct. Struktura może zawierać typy HIDL, w tym handle, stringvec<T>, które wskazują na oddzielne bufory o zmiennej długości.

nick

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.

Typ handle jest reprezentowany przez strukturę hidl_handle w C++, która jest prostym opakowaniem wskaźnika do obiektu const native_handle_t (jest on obecny w Androidzie od dawna).

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 własności wskazywanego przez siebie wskaźnika native_handle_t. Służy on tylko do bezpiecznego przechowywania wskaźnika do native_handle_t, aby można go było używać w procesach 32- i 64-bitowych.

Przykłady scenariuszy, w których hidl_handle jest właścicielem załączonych plików:

  • 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ę kopiowania z innego obiektu hidl_handle,
  • Gdy obiekt hidl_handle jest przypisany do innego obiektu hidl_handle

hidl_handle zapewnia konwersje do i z obiektów native_handle_t* (zarówno domyślne, jak i jawne). Głównym zastosowaniem typu handle w HIDL jest przekazywanie deskryptorów plików przez interfejsy HIDL. Pojedynczy plik jest więc reprezentowany przez native_handle_t bez int i jednym fd. Jeśli klient i serwer działają w ramach różnych procesów, implementacja RPC automatycznie zajmuje się deskryptorem pliku, aby zapewnić, że oba procesy mogą działać na tym samym pliku.

Identyfikator pliku otrzymany w ramach procesu hidl_handle jest prawidłowy w tym procesie, ale nie jest przechowywany po zakończeniu działania funkcji (jest zamykany po zakończeniu działania funkcji). Proces, który chce zachować trwały dostęp do deskryptora pliku, musi dup()zamknięte deskryptory plików lub skopiować cały obiekt hidl_handle.

Pamięć

Typ HIDL memory jest mapowany na klasę hidl_memorylibhidlbase, która reprezentuje niezamapowaną pamięć współdzieloną. Jest to obiekt, który musi być przekazywany między procesami w celu udostępniania pamięci w HIDL. Aby użyć współdzielonej pamięci:

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

IAllocator może służyć do przydzielania pamięci:

#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ć wprowadzane za pomocą obiektu IMemory, albo po stronie, która utworzyła mem, albo po stronie, która otrzymuje go za pomocą 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 mogą być przekazywane jako obiekty. Słowo interface może być używane jako cukier syntaktyczny dla typu android.hidl.base@1.0::IBase. Oprócz tego bieżący interfejs i wszystkie zaimportowane interfejsy są definiowane jako typ.

Zmienne, które przechowują interfejsy, powinny być silnymi wskaźnikami:sp<IName>. Funkcje HIDL, które przyjmują parametry interfejsu, zamieniają surowe wskaźniki w silne wskaźniki, powodując nieintuicyjne działanie (wskaźnik może zostać nieoczekiwanie wyczyszczony). Aby uniknąć problemów, zawsze przechowuj interfejsy HIDL jako sp<>.