本節介紹 HIDL 數據類型。有關實現詳細信息,請參閱HIDL C++ (用於 C++ 實現)或HIDL Java (用於 Java 實現)。
與 C++ 的相似之處包括:
-
structs
使用 C++ 語法;unions
默認支持 C++ 語法。兩者都必須命名;不支持匿名結構和聯合。 - HIDL 中允許使用 Typedef(就像在 C++ 中一樣)。
- 允許使用 C++ 樣式的註釋並將其複製到生成的頭文件中。
與 Java 的相似之處包括:
- 對於每個文件,HIDL 定義了一個必須以
android.hardware.
.生成的 C++ 命名空間是::android::hardware::…
。 - 該文件的所有定義都包含在 Java 風格的
interface
包裝器中。 - HIDL 數組聲明遵循 Java 樣式,而不是 C++ 樣式。示例:
struct Point { int32_t x; int32_t y; }; Point[3] triangle; // sized array
- 註釋類似於 javadoc 格式。
數據表示
由標準佈局(普通舊數據類型要求的子集)組成的struct
或union
在生成的 C++ 代碼中具有一致的內存佈局,並通過struct
和union
成員的顯式對齊屬性強制執行。
原始 HIDL 類型以及enum
和bitfield
類型(始終從原始類型派生)映射到標準 C++ 類型,例如來自cstdint的std::uint32_t
。
由於 Java 不支持無符號類型,因此無符號 HIDL 類型將映射到相應的有符號 Java 類型。結構映射到 Java 類;數組映射到 Java 數組; Java 目前不支持聯合。字符串在內部存儲為 UTF8。由於 Java 僅支持 UTF16 字符串,因此發送到 Java 實現或從 Java 實現發送的字符串值會被轉換,並且在重新轉換時可能會不相同,因為字符集並不總是平滑映射。
在 C++ 中通過 IPC 接收的數據標記為const
,並且位於只讀內存中,僅在函數調用期間持續存在。在 Java 中通過 IPC 接收的數據已經被複製到 Java 對像中,因此可以保留它而無需額外複製(並且可以修改)。
註釋
Java 風格的註解可以添加到類型聲明中。註釋由 HIDL 編譯器的供應商測試套件 (VTS) 後端解析,但 HIDL 編譯器實際上不理解任何此類解析的註釋。相反,已解析的 VTS 註釋由 VTS 編譯器 (VTSC) 處理。
註釋使用 Java 語法: @annotation
或@annotation(value)
或@annotation(id=value, id=value…)
其中 value 可以是常量表達式、字符串或{}
內的值列表,就像在爪哇。同名的多個註釋可以附加到同一個項目。
前向聲明
在 HIDL 中,可能無法前向聲明結構,這使得用戶定義的自引用數據類型成為不可能(例如,您無法在 HIDL 中描述鍊錶或樹)。大多數現有(Android 8.x 之前的)HAL 對前向聲明的使用有限,可以通過重新排列數據結構聲明來刪除前向聲明。
此限制允許使用簡單的深度複製按值複製數據結構,而不是跟踪可能在自引用數據結構中多次出現的指針值。如果相同的數據被傳遞兩次,例如使用兩個方法參數或指向相同數據的vec<T>
,則製作並傳遞兩個單獨的副本。
嵌套聲明
HIDL 支持任意級別的嵌套聲明(下文指出一個例外)。例如:
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>
中並且只有一層深(沒有vec<vec<IFoo>>
)。
原始指針語法
HIDL 語言不使用*並且不支持 C/C++ 原始指針的全部靈活性。有關 HIDL 如何封裝指針和數組/向量的詳細信息,請參閱vec<T> 模板。
接口
interface
關鍵字有兩種用法。
- 它在 .hal 文件中打開接口的定義。
- 它可以用作結構/聯合字段、方法參數和返回中的特殊類型。它被視為通用接口和
android.hidl.base@1.0::IBase
的同義詞。
例如, IServiceManager
有以下方法:
get(string fqName, string name) generates (interface service);
該方法承諾按名稱查找某些接口。將 interface 替換為android.hidl.base@1.0::IBase
也是相同的。
接口只能以兩種方式傳遞:作為頂級參數,或作為vec<IMyInterface>
的成員。它們不能是嵌套的 vecs、結構、數組或聯合的成員。
MQDescriptorSync & MQDescriptorUnsync
MQDescriptorSync
和MQDescriptorUnsync
類型通過 HIDL 接口傳遞同步或非同步的快速消息隊列 (FMQ) 描述符。有關詳細信息,請參閱HIDL C++ (Java 不支持 FMQ)。
內存類型
memory
類型用於表示 HIDL 中未映射的共享內存。它僅在 C++ 中受支持。這種類型的值可以在接收端用於初始化IMemory
對象,映射內存並使其可用。有關詳細信息,請參閱HIDL C++ 。
警告:放置在共享內存中的結構化數據必須是其格式在傳遞memory
的接口版本的生命週期內永遠不會改變的類型。否則,HAL 可能會遇到致命的兼容性問題。
指針類型
pointer
類型僅供 HIDL 內部使用。
bitfield<T> 類型模板
其中T
是用戶定義的枚舉的bitfield<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);
編譯器處理類型標誌與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
用於共享內存、實際文件或任何其他可以隱藏的東西一個文件描述符。 binder 驅動程序將文件描述符複製到另一個進程中。
native_handle_t
Android 支持native_handle_t
,這是在libcutils
中定義的通用句柄概念。
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;
本機句柄是整數和文件描述符的集合,它們按值傳遞。單個文件描述符可以存儲在沒有整數和單個文件描述符的本機句柄中。使用封裝了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)將字符串傳入或傳出 Java 將導致字符集轉換可能無法完全保留原始編碼。
vec<T> 類型模板
vec<T>
模板表示包含T
實例的可變大小緩衝區。
T
可以是以下之一:
- 原始類型(例如 uint32_t)
- 字符串
- 用戶定義的枚舉
- 用戶定義的結構
- 接口或
interface
關鍵字(vec<IFoo>
,vec<interface>
僅支持作為頂級參數) - 把手
- 位域<U>
- vec<U>,其中 U 在此列表中,但接口除外(例如,不支持
vec<vec<IFoo>>
) - U[](U 的大小數組),其中 U 在此列表中,接口除外
用戶定義類型
本節介紹用戶定義的類型。
枚舉
HIDL 不支持匿名枚舉。否則,HIDL 中的枚舉類似於 C++11:
enum name : type { enumerator , enumerator = constexpr , … }
基本枚舉是根據 HIDL 中的一種整數類型定義的。如果沒有為基於整數類型的枚舉的第一個枚舉器指定值,則該值默認為 0。如果沒有為後面的枚舉器指定值,則該值默認為前一個值加一。例如:
// RED == 0 // BLUE == 4 (GREEN + 1) enum Color : uint32_t { RED, GREEN = 3, BLUE }
枚舉也可以從先前定義的枚舉繼承。如果沒有為子枚舉的第一個枚舉器(在本例中為FullSpectrumColor
)指定值,則默認為父枚舉的最後一個枚舉器的值加一。例如:
// 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 不支持完全包含在結構中的可變長度數據結構。這包括有時用作 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 }