このセクションでは、HIDL のデータ型について説明します。実装の詳細については、HIDL C++(C++ 実装の場合)または HIDL Java(Java 実装の場合)をご覧ください。
C++ との類似点は次のとおりです。
structs
は C++ 構文を使用し、unions
はデフォルトで C++ 構文をサポートしています。どちらも名前を指定する必要があります。匿名の構造体と共用体はサポートされません。- typedef は(C++ であるため)HIDL で使用できます。
- C++ スタイルのコメントを使用できます。それらは生成されたヘッダー ファイルにコピーされます。
Java との類似点は次のとおりです。
- HIDL では、ファイルごとに Java スタイルの名前空間(
android.hardware.
で始まる必要がある)が定義されます。生成される C++ 名前空間は::android::hardware::…
です。 - ファイルのすべての定義は、Java スタイルの
interface
ラッパーに含まれます。 - HIDL 配列宣言は、C++ スタイルではなく Java スタイルに従います。例:
struct Point { int32_t x; int32_t y; }; Point[3] triangle; // sized array
- コメントは javadoc 形式と同様です。
データ表現
標準レイアウト(plain-old-data 型の要件のサブセット)で構成された struct
または union
は、生成された C++ コードで一貫したメモリ レイアウトを維持し、struct
および union
メンバーに明示的な配置属性を適用します。
プリミティブ HIDL 型と、(常にプリミティブ型から派生する)enum
および bitfield
型は、標準の C++ 型(cstdint の std::uint32_t
など)にマッピングされます。
Java では符号なし型がサポートされないため、符号なしの HIDL 型は対応する Java の符号付き型にマッピングされます。構造体は Java クラスにマッピングされ、配列は Java 配列にマッピングされます。共用体は Java では現在サポートされていません。文字列は内部的には UTF8 として保存されます。Java は UTF16 文字列のみをサポートするため、Java 実装との間で送受信される文字列値は変換されます。文字セットは必ずしも完全にマッピングされるわけではないため、再変換すると元と同じにならない場合があります。
C++ で IPC を介して受信したデータは const
としてマークされ、関数呼び出しの存続期間のみ保持される読み取り専用メモリ内に配置されます。Java で IPC を介して受信したデータは Java オブジェクトにすでにコピーされているため、追加でコピーすることなく保持(および変更)できます。
アノテーション
Java スタイルのアノテーションを型宣言に追加できます。アノテーションは、HIDL コンパイラのベンダー テストスイート(VTS)バックエンドによって解析されますが、解析されたアノテーションは実際には HIDL コンパイラにより認識されるわけではありません。解析された VTS アノテーションは、VTS コンパイラ(VTSC)によって処理されます。
アノテーションは Java 構文 @annotation
、@annotation(value)
、@annotation(id=value, id=value…)
を使用します。値は Java と同様に、定数式、文字列、または {}
内の値リストのいずれかです。同じ名前の複数のアノテーションを同じ項目に付加することができます。
前方宣言
HIDL では構造体を前方宣言できないため、ユーザー定義の自己参照データ型は使用できません(たとえば、HIDL でリンクリストやツリーを記述することはできません)。前方宣言は、既存(Android 8.x より前)の HAL のほとんどで使用が制限されており、データ構造の宣言の並べ替えにより取り除くことができます。
この制限により、自己参照データ構造で複数回発生する可能性があるポインタ値をトラッキングする代わりに、単純なディープコピーでデータ構造を値ごとにコピーできます。同じデータを指す 2 つのメソッド パラメータや vec<T>
など、同じデータが 2 回渡される場合は、2 つのコピーが別々に作成されて送信されます。
ネストされた宣言
HIDL は、必要な数のレベルにネストされた宣言をサポートしています(後述の例外が 1 つあります)。次に例を示します。
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 } …
例外として、インターフェース型は、vec<T>
にのみ、1 レベルだけ埋め込むことができます(vec<vec<IFoo>>
は不可)。
未加工ポインタの構文
HIDL 言語では * は使用されず、C / C++ の未加工ポインタの完全な柔軟性はサポートされません。HIDL でのポインタと配列 / ベクトルのカプセル化方法については、vec<T> テンプレートをご覧ください。
インターフェース
interface
キーワードには 2 つの用途があります。
- .hal ファイルのインターフェースの定義を開きます。
- 構造体と共用体のフィールド、メソッド パラメータ、戻り値で特殊な型として使用できます。一般的なインターフェースとして
android.hidl.base@1.0::IBase
の同義語であると見なされます。
たとえば、IServiceManager
には次のメソッドがあります。
get(string fqName, string name) generates (interface service);
このメソッドでは、一部のインターフェースを名前で検索できます。これは、インターフェースを android.hidl.base@1.0::IBase
に置き換えるのと同じです。
インターフェースを渡す方法は、トップレベル パラメータとして渡すか、vec<IMyInterface>
のメンバーとして渡すかの 2 つのみです。ネストされたベクトル、構造体、配列、共用体のメンバーにすることはできません。
MQDescriptorSync と MQDescriptorUnsync
MQDescriptorSync
型と MQDescriptorUnsync
型は、HIDL インターフェース全体で同期または非同期の高速メッセージ キュー(FMQ)記述子を渡します。詳細については、HIDL C++ をご覧ください(FMQ は Java ではサポートされていません)。
memory 型
memory
型は、HIDL でマッピングされていない共有メモリを表すために使用されます。C++ でのみサポートされます。この型の値を受信側で使用して IMemory
オブジェクトを初期化し、メモリをマッピングして使用可能にできます。詳細については、HIDL C++ をご覧ください。
警告: 共有メモリに配置される構造化データは、memory
を渡すインターフェース バージョンの存続期間を通じて形式が変化しない型でなければなりません。そのような型でない場合、HAL に致命的な互換性の問題が生じる可能性があります。
pointer 型
pointer
型は HIDL の内部使用専用です。
bitfield<T> 型テンプレート
bitfield<T>
(T
はユーザー定義列挙型)は、値が T
で定義されている列挙値のビット和であることを示しています。生成されたコードでは、bitfield<T>
は T の基になる型として表現されます。次に例を示します。
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);
コンパイラは、Flags 型を uint8_t
と同様に処理します。
(u)int8_t
/ (u)int16_t
/ (u)int32_t
/ (u)int64_t
を使用しない理由は、bitfield
を使用すると追加の HAL 情報がリーダーに提供されるためです。これにより、リーダーは setFlags
が Flag のビット和値を取る(つまり、16 を指定した setFlags
の呼び出しが無効である)ことを認識します。bitfield
を使用しなければ、この情報はドキュメントでのみ伝えられることになります。さらに、VTS は flags の値が Flag のビット和であるかどうかを実際に確認できます。
プリミティブ型ハンドル
警告: どのような種類のアドレスも(物理デバイスのアドレスであっても)、決してネイティブ ハンドルに含めないでください。プロセス間でこの情報を渡すことは危険性が高く、攻撃を受けやすくなります。プロセス間で渡される値は、プロセス内の割り当て済みメモリの検索に使用する前に、検証が必要です。検証しないと、不正なハンドルによって不正なメモリアクセスやメモリ破損が発生するおそれがあります。
HIDL セマンティクスは値ごとにコピーされます。これは、パラメータがコピーされることを意味します。大量のデータまたはプロセス間で共有する必要があるデータ(同期フェンスなど)は、永続オブジェクトを指すファイル記述子(共有メモリや実際のファイルなど、ファイル記述子の背後に隠れている可能性があるものについては ashmem
)を渡すことで処理されます。バインダ ドライバは、ファイル記述子を他のプロセスに複製します。
native_handle_t
Android は、libcutils
で定義された一般的なハンドルのコンセプトである native_handle_t
をサポートしています。
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;
ネイティブ ハンドルは、値によって渡される int とファイル記述子のコレクションです。1 つのファイル記述子を、int がなくファイル記述子が 1 つあるネイティブ ハンドルに格納できます。handle
プリミティブ型でカプセル化されたネイティブ ハンドルを使用してハンドルを渡すと、ネイティブ ハンドルが HIDL に直接含まれるようになります。
native_handle_t
はサイズが変わるため、構造体に直接含めることはできません。ハンドル フィールドでは、個別に割り当てられた native_handle_t
を指すポインタが生成されます。
以前のバージョンの Android では、libcutils にある同じ関数を使用してネイティブ ハンドルが作成されていました。Android 8.0 以上では、これらの関数は android::hardware::hidl
名前空間にコピーされるか、NDK に移されました。これらの関数は、HIDL 自動生成コードにより、ユーザー作成コードの関与なしで、自動的にシリアル化および逆シリアル化されます。
ハンドルとファイル記述子のオーナー権限
hidl_handle
オブジェクト(トップレベルまたは複合型の一部)を渡す(または返す)HIDL インターフェース メソッドを呼び出す場合、これに含まれるファイル記述子のオーナー権限は次のようになります。
- 引数として
hidl_handle
オブジェクトを渡す呼び出し元は、ラップしたnative_handle_t
に含まれているファイル記述子のオーナー権限を保持します。呼び出し元はファイル記述子の処理を終えたときにそれらを閉じる必要があります。 hidl_handle
オブジェクトを(_cb
関数に渡すことで)返すプロセスは、オブジェクトでラップされたnative_handle_t
に含まれているファイル記述子のオーナー権限を保持します。プロセスはファイル記述子の処理を終えたときにそれらを閉じる必要があります。hidl_handle
を受け取るトランスポートは、オブジェクトでラップされたnative_handle_t
内のファイル記述子のオーナー権限を保持します。受信側はトランザクション コールバック中にそれらのファイル記述子をそのまま使用できますが、コールバック後にファイル記述子を使用するには、ネイティブ ハンドルのクローンを作成する必要があります。トランザクションが完了すると、トランスポートは自動的にファイル記述子用にclose()
を呼び出します。
HIDL は Java でハンドルをサポートしません(Java がハンドルをまったくサポートしていないため)。
サイズ指定される配列
HIDL 構造体のサイズ指定される配列の要素には、構造体が格納できる任意の型を使用できます。
struct foo { uint32_t[3] x; // array is contained in foo };
文字列
C++ と Java では文字列の表現が異なりますが、基になるトランスポート ストレージ タイプは C++ 構造です。詳細については、HIDL C++ データ型または HIDL Java データ型をご覧ください。
注: HIDL インターフェースを使用して文字列を Java との間(Java 同士を含む)でやり取りすると、文字セットの変換が発生し、元のエンコードを保持できない可能性があります。
vec<T> 型テンプレート
vec<T>
テンプレートは、T
のインスタンスを含む可変サイズのバッファを表します。
T
は次のいずれかです。
- プリミティブ型(例: uint32_t)
- 文字列
- ユーザー定義列挙型
- ユーザー定義構造体
- インターフェース、または
interface
キーワード(vec<IFoo>
、vec<interface>
はトップレベル パラメータとしてのみサポートされます) - ハンドル
- bitfield<U>
- vec<U>(U にはこのリストのインターフェース以外のものを使用できます。たとえば、
vec<vec<IFoo>>
はサポートされません) - U[](U のサイズ指定される配列。U にはこのリストのインターフェース以外のものを使用できます)
ユーザー定義型
このセクションでは、ユーザー定義型について説明します。
列挙型
HIDL は匿名の列挙型をサポートしていません。それ以外の点では、HIDL の列挙型は C++11 に似ています。
enum name : type { enumerator , enumerator = constexpr , … }
基本列挙型は、HIDL の整数型の 1 つで定義されます。整数型に基づく列挙型では、最初の列挙子に値が指定されていない場合、デフォルトの値は 0 です。それより後の列挙子に値が指定されていない場合、デフォルトの値は前の値に 1 を足した値です。次に例を示します。
// RED == 0 // BLUE == 4 (GREEN + 1) enum Color : uint32_t { RED, GREEN = 3, BLUE }
列挙型は、以前に定義した列挙型を継承することもできます。子列挙型(この例では FullSpectrumColor
)の最初の列挙子に値が指定されていない場合、デフォルトの値は親列挙型の最後の列挙子の値に 1 を足した値です。次に例を示します。
// ULTRAVIOLET == 5 (Color:BLUE + 1) enum FullSpectrumColor : Color { ULTRAVIOLET }
警告: 列挙型の継承は、他のほとんどの種類の継承とは逆方向に機能します。子列挙値を親列挙値として使用することはできません。これは、子列挙型には親より多くの値が含まれているためです。一方、親列挙値は子列挙値として安全に使用できます。これは、定義上、子列挙値は親列挙値のスーパーセットであるためです。つまり、親列挙型を参照する型は、インターフェースのその後の反復で子列挙型を参照できません。インターフェースの設計時には、この点に留意してください。
列挙型の値は、ネストされた型のようなドット構文ではなく、コロン構文で参照されます。構文は Type:VALUE_NAME
です。値が同じ列挙型または子列挙型の中で参照される場合、型を指定する必要はありません。例:
enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 }; enum Color : Grayscale { RED = WHITE + 1 }; enum Unrelated : uint32_t { FOO = Color:RED + 1 };
Android 10 以降、列挙型には定数式で使用できる len
属性が含まれています。MyEnum::len
は、その列挙型のエントリの合計数です。これは値の合計数とは異なり、値が重複している場合は小さくなることがあります。
構造体
HIDL は匿名の構造体をサポートしていません。それ以外の点では、HIDL の構造体は C と非常によく似ています。
HIDL は、1 つの構造体に完全に含まれる可変長データ構造をサポートしていません。これには、C/C++ で構造体の最後のフィールドとして使用されることがある不定長の配列が含まれます(サイズが [0]
の場合もあります)。HIDL の vec<T>
は、別個のバッファに格納されたデータで動的にサイズ変更される配列を表現します。このようなインスタンスは、struct
内の vec<T>
のインスタンスで表現されます。
同様に、string
を struct
に含めることができます(関連バッファは独立しています)。生成された C++ では、HIDL ハンドル型のインスタンスは、基になるデータ型のインスタンスが可変長であるため、実際のネイティブ ハンドルへのポインタとして表現されます。
共用体
HIDL は匿名の共用体をサポートしていません。それ以外の点では、共用体は C と似ています。
共用体には、修正型(ポインター、ファイル記述子、バインダー オブジェクトなど)を含めることはできません。特別なフィールドまたは関連型は不要で、単に memcpy()
(または同等の方法)を使用してコピーします。共用体には、バインダー オフセット(つまり、ハンドルまたはバインダー インターフェース参照)の設定を必要とするものが直接含まれていない(または他のデータ構造を使用して含まれていない)場合があります。次に例を示します。
union UnionType { uint32_t a; // vec<uint32_t> r; // Error: can't contain a vec<T> uint8_t b;1 }; fun8(UnionType info); // Legal
共用体は、構造体内で宣言することもできます。次に例を示します。
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 }