本節介紹 HIDL 資料類型。有關實作詳細信息,請參閱HIDL C++ (對於 C++ 實作)或HIDL Java (對於 Java 實作)。
與 C++ 的相似之處包括:
-
structs
使用C++語法;unions
預設支援 C++ 語法。兩者都必須註明姓名;不支援匿名結構和聯合。 - HIDL 中允許使用 Typedef(與 C++ 中一樣)。
- 允許使用 C++ 風格的註釋,並將其複製到產生的頭檔中。
與 Java 的相似之處包括:
- 對於每個文件,HIDL 定義一個 Java 風格的命名空間,該命名空間必須以
android.hardware.
。產生的 C++ 命名空間為::android::hardware::…
。 - 該檔案的所有定義都包含在 Java 風格的
interface
包裝器中。 - HIDL 陣列宣告遵循 Java 風格,而不是 C++ 風格。範例:
struct Point { int32_t x; int32_t y; }; Point[3] triangle; // sized array
- 註解類似於javadoc格式。
數據表示
由標準佈局(普通舊資料類型要求的union
)組成的struct
或聯合體在產生的 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> template 。
介面
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>
的成員。它們不能是巢狀向量、結構、陣列或聯合的成員。
MQDescriptorSync 和 MQDescriptorUnsync
MQDescriptorSync
和MQDescriptorUnsync
類型透過 HIDL 介面傳遞同步或不同步的快速訊息佇列 (FMQ) 描述符。有關詳細信息,請參閱HIDL C++ (Java 不支援 FMQ)。
記憶體型
memory
類型用於表示 HIDL 中未映射的共享記憶體。它僅在 C++ 中支援。這種類型的值可以在接收端用來初始化IMemory
對象,映射記憶體並使其可用。有關詳細信息,請參閱HIDL C++ 。
警告:放置在共享記憶體中的結構化資料必須是其格式在傳遞memory
的介面版本的生命週期內永遠不會改變的類型。否則,HAL 可能會遇到致命的相容性問題。
指標型
pointer
類型僅供 HIDL 內部使用。
位元域<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 支援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 }