HIDL データ宣言により、C++ 標準レイアウト データ構造が生成されます。これらの構造は、適切と思われる任意の場所(スタック、ファイルまたはグローバル スコープ、ヒープ)に配置でき、同じ方法で構成できます。クライアント コードは HIDL プロキシ コードを呼び出して const 参照とプリミティブ型を渡しますが、スタブ コードとプロキシ コードはシリアル化の詳細を隠蔽します。
注: データ構造を明示的にシリアル化または逆シリアル化するためのコードをデベロッパーが作成する必要は一切ありません。
次の表は、HIDL プリミティブを C++ データ型にマッピングしたものです。
| HIDL 型 | C++ 型 | ヘッダー / ライブラリ |
|---|---|---|
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 |
以下のセクションでは、データ型について詳しく説明します。
enum
HIDL の列挙型は C++ の列挙型になります。次に例を示します。
enum Mode : uint8_t { WRITE = 1 << 0, READ = 1 << 1 }; enum SpecialMode : Mode { NONE = 0, COMPARE = 1 << 2 };
これは次のようになります。
enum class Mode : uint8_t { WRITE = 1, READ = 2 }; enum class SpecialMode : uint8_t { WRITE = 1, READ = 2, NONE = 0, COMPARE = 4 };
Android 10 以降では、::android::hardware::hidl_enum_range を使用して列挙型を反復処理できます。この範囲には、親列挙型から最後の子までのすべての列挙子が、HIDL ソースコードに現れる順序で含まれます。たとえば、次のコードは、WRITE、READ、NONE、COMPARE をこの順序で反復処理します。上述の SpecialMode の場合、次のようになります。
template <typename T> using hidl_enum_range = ::android::hardware::hidl_enum_range<T> for (SpecialMode mode : hidl_enum_range<SpecialMode>) {...}
hidl_enum_range は逆反復子も実装しており、constexpr コンテキストで使用できます。ある列挙内に値が複数回現れる場合、値はその範囲内に複数回現れます。
bitfield<T>
bitfield<T>(T はユーザー定義の列挙型)は C++ でその列挙型の基になる型となります。上記の例では、bitfield<Mode> は uint8_t になります。
vec<T>
hidl_vec<T> クラス テンプレートは libhidlbase の一部で、任意のサイズの任意の HIDL 型のベクトルを渡すために使用できます。同等の固定サイズのコンテナは hidl_array です。hidl_vec::setToExternal() 関数を使用して、T 型の外部データバッファを指すように hidl_vec<T> を初期化することもできます。
生成された C++ ヘッダーに構造体を適切に出力 / 挿入するほか、vec<T> の使用により、std::vector とベア T ポインタを相互変換する便利な関数が生成されます。パラメータに vec<T> を使用した場合、これを使用する関数がオーバーロードされ(2 つのプロトタイプを生成)、パラメータの HIDL 構造体と std::vector<T> 型の両方の受け渡しが行われます。
配列
HIDL の定数配列は、libhidlbase の hidl_array クラスで表されます。hidl_array<T, S1, S2, …,
SN> は、N 次元の固定サイズ配列 T[S1][S2]…[SN] を表します。
文字列
hidl_string クラス(libhidlbase の一部)は、HIDL インターフェースを介して文字列を渡すために使用可能で、/system/libhidl/base/include/hidl/HidlSupport.h で定義されています。クラス内の最初の保存場所は、その文字バッファへのポインタです。
hidl_string は、operator=、暗黙的なキャスト、.c_str() 関数を使用して、std::string and char*(C スタイルの文字列)と相互変換する方法を認識します。HIDL 文字列構造体には、以下の処理を行うための適切なコピー コンストラクタと割り当て演算子があります。
std::stringまたは C 文字列から HIDL 文字列を読み込む。- HIDL 文字列から新しい
std::stringを作成する。
また、HIDL 文字列には変換コンストラクタがあるので、HIDL 文字列を受け取るメソッドで C 文字列(char *)と C++ 文字列(std::string)を使用できます。
構造体
HIDL の struct に含めることができるのは固定サイズのデータ型のみです。関数を含めることはできません。HIDL 構造体の定義は、C++ の標準レイアウトの struct に直接マッピングされます。これにより、struct が一貫したメモリ レイアウトを持つことが保証されます。構造体には、個別の可変長バッファを指す handle、string、vec<T> などの HIDL 型を含めることができます。
ハンドル
警告: どのような種類のアドレスも(物理デバイスのアドレスであっても)、決してネイティブ ハンドルに含めないでください。プロセス間でこの情報を渡すことは危険性が高く、攻撃を受けやすくなります。プロセス間で渡される値は、プロセス内の割り当て済みメモリの検索に使用する前に、検証が必要です。検証を行わない場合、不正なハンドルによって不正なメモリアクセスやメモリ破損が発生するおそれがあります。
handle 型は、const native_handle_t オブジェクト(これは Android にかなり以前から存在していました)へのポインタの単純なラッパーである、C++ の hidl_handle 構造体によって表されます。
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;
デフォルトでは、hidl_handle はラップする native_handle_t ポインタのオーナー権限を取得しません。このハンドルは、32 ビットプロセスと 64 ビットプロセスで使用できるように、native_handle_t へのポインタを安全に格納するためだけに存在します。
hidl_handle が自身の包含されたファイル記述子を所有するのは、次のような状況です。
shouldOwnパラメータをtrueに設定してsetTo(native_handle_t* handle, bool shouldOwn)メソッドを呼び出した後hidl_handleオブジェクトが別のhidl_handleオブジェクトからのコピー コンストラクションによって作成されたときhidl_handleオブジェクトが別のhidl_handleオブジェクトからコピー割り当てされたとき
hidl_handle は、native_handle_t* オブジェクトとの暗黙的および明示的な相互変換を提供します。HIDL での handle 型の主な役割は、HIDL インターフェースを介してファイル記述子を渡すことです。したがって、単一のファイル記述子は、int のない native_handle_t と単一の fd で表されます。クライアントとサーバーが別のプロセスに存在する場合、RPC の実装は自動的にファイル記述子を処理し、両方のプロセスが同一のファイルで動作できるようにします。
プロセスによって hidl_handle で取得されたファイル記述子はそのプロセス内で有効になりますが、受信側の関数の存続期間を超える保持は行われません(関数が返るとクローズします)。ファイル記述子への永続的なアクセスを保持しようとするプロセスは、包含されたファイル記述子を dup() するか、hidl_handle オブジェクト全体をコピーする必要があります。
メモリ
HIDL の memory 型は、マッピングされていない共有メモリを表す libhidlbase の hidl_memory クラスにマッピングされます。これは、HIDL でメモリを共有するためにプロセス間で渡す必要があるオブジェクトです。共有メモリを使用するには、次の手順を実施します。
IAllocatorのインスタンス(現在は「ashmem」インスタンスのみが利用可能です)を取得し、これを使用して共有メモリを割り当てます。IAllocator::allocate()はhidl_memoryオブジェクトを返します。このオブジェクトは、HIDL RPC を介して渡し、libhidlmemoryのmapMemory関数を使用してプロセスにマッピングすることができます。mapMemoryは、メモリへのアクセスに使用できるsp<IMemory>オブジェクトへの参照を返します(IMemoryとIAllocatorはandroid.hidl.memory@1.0で定義されます)。
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 }));
メモリに対する実際の変更は、mem を作成した側または HIDL RPC 経由でそれを受信する側で、IMemory オブジェクトを介して実行する必要があります。
// 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();
インターフェース
インターフェースはオブジェクトとして渡すことができます。interface という言葉を、型 android.hidl.base@1.0::IBase の糖衣構文として使用できます。さらに、現在のインターフェースおよびインポートされたすべてのインターフェースは、型として定義されます。
インターフェースを保持する変数は、sp<IName> のような強いポインタでなければなりません。インターフェース パラメータを受け取る HIDL 関数は、未加工ポインタを強いポインタに変換します。これにより、直感的でない動作が引き起こされます(ポインタが予期せず消去されることがあります)。問題を回避するには、HIDL インターフェースを常に sp<> として格納してください。