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
, NONE
i COMPARE
. 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_array
w libhidlbase
. 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
, string
i vec<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 parametremshouldOwn
ustawionym natrue
- gdy obiekt
hidl_handle
jest tworzony przez konstrukcję kopiowania z innego obiektuhidl_handle
, - Gdy obiekt
hidl_handle
jest przypisany do innego obiektuhidl_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_memory
w libhidlbase
, 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:
- Uzyskaj instancję
IAllocator
(obecnie dostępna jest tylko instancja „ashmem”) i użyj jej do przydzielenia pamięci współdzielonej. IAllocator::allocate()
zwraca obiekthidl_memory
, który można przekazać przez interfejs HIDL RPC i przemapować na proces za pomocą funkcjimapMemory
klasylibhidlmemory
.mapMemory
zwraca odwołanie do obiektusp<IMemory>
, którego można użyć do uzyskania dostępu do pamięci. (ParametryIMemory
iIAllocator
są zdefiniowane w dokumentacjiandroid.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<>
.