資料類型

本節將說明 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 格式。

資料表示

Standard-Layout (plain-old-data 類型需求的子集) 組成的 structunion,在產生的 C++ 程式碼中具有一致的記憶體版面配置,並在 structunion 成員上強制執行明確的對齊屬性。

基本 HIDL 型別,以及 enumbitfield 型別 (一律衍生自基本型別),會對應至標準 C++ 型別,例如 cstdint 中的 std::uint32_t

由於 Java 不支援未簽署的類型,因此未簽署的 HIDL 類型會對應至相應的已簽署 Java 類型。Structs 會對應至 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 中描述連結清單或樹狀結構)。大多數現有的 HAL (Android 8.x 之前) 僅限使用前向宣告,您可以重新排列資料結構宣告來移除這些宣告。

這項限制可讓資料結構以簡單的深層複製方式以值複製,而非追蹤在自參照資料結構中可能出現多次的指標值。如果傳遞相同的資料兩次 (例如,使用兩個方法參數或指向相同資料的 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);

該方法會依名稱查詢「某些介面」。這與使用 android.hidl.base@1.0::IBase 取代介面相同。

介面只能以兩種方式傳遞:做為頂層參數,或做為 vec<IMyInterface> 的成員。但不能是巢狀 vec、結構體、陣列或聯集的成員。

MQDescriptorSync 和 MQDescriptorUnsync

MQDescriptorSyncMQDescriptorUnsync 類型會透過 HIDL 介面傳遞已同步或未同步的快速訊息佇列 (FMQ) 描述元。詳情請參閱 HIDL C++ (Java 不支援 FMQ)。

記憶體類型

memory 類型用於代表 HIDL 中的未對應共用記憶體。這個類型僅支援 C++。接收端可使用這類型別的值來初始化 IMemory 物件,對應記憶體並使其可用。詳情請參閱 HIDL C++

警告:放置在共用記憶體中的結構化資料,其格式必須在傳遞 memory 的介面版本生命週期中保持不變。否則 HAL 可能會發生嚴重相容性問題。

指標類型

pointer 類型僅供 HIDL 內部使用。

bitfield<T> 型別範本

bitfield<T> 中的 T使用者定義的 enum,表示該值是 T 中定義的 enum 值的位元運算 OR。在產生的程式碼中,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 相同的方式處理 Flags 類型。

為何不使用 (u)int8_t/(u)int16_t/(u)int32_t/(u)int64_t?使用 bitfield 可為讀者提供額外的 HAL 資訊,讓讀者知道 setFlags 會採用 Flag 的位元運算 OR 值 (也就是知道以 16 呼叫 setFlags 是無效的)。如果沒有 bitfield,這項資訊只能透過文件傳達。此外,VTS 實際上可以檢查旗標的值是否為 Flag 的位元運算 OR。

原始類型句柄

警告:任何類型的位址 (包括實體裝置位址) 都不得是原生句柄的一部分。在程序之間傳遞這類資訊相當危險,且容易遭到攻擊。在程序之間傳遞的任何值,都必須先經過驗證,才能用於在程序中查詢已分配的記憶體。否則,不良的句柄可能會導致記憶體存取錯誤或記憶體損毀。

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;

原生句柄是一系列整數和檔案描述元,會透過值傳遞。單一檔案描述元資料可儲存在原生句柄中,且不含 int 和單一檔案描述元資料。使用以 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> 僅支援做為頂層參數)
  • 帳號代碼
  • bitfield<U>
  • vec<U>,其中 U 是此清單中的介面 (例如 vec<vec<IFoo>> 不受支援)
  • U[] (U 的大小陣列),其中 U 是此清單中的項目,但不包括介面

使用者定義的類型

本節將說明使用者定義的類型。

Enum

HIDL 不支援匿名列舉。除此之外,HIDL 中的列舉與 C++11 相似:

enum name : type { enumerator , enumerator = constexpr ,   }

基本列舉項目是根據 HIDL 中的其中一個整數類型定義。如果您未為以整數型別為基礎的列舉項目的第一個枚舉器指定值,則該值預設為 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 不支援完全包含在結構體內的變數長度資料結構。這包括不定長度的陣列,有時會用於 C/C++ 中結構體的最後一個欄位 (有時會顯示為 [0] 大小)。HIDL vec<T> 會以動態大小的陣列代表資料,這些資料儲存在個別緩衝區中;這類例項會以 struct 中的 vec<T> 例項表示。

同樣地,string 可包含在 struct 中 (相關聯的緩衝區是分開的)。在產生的 C++ 中,HIDL 句柄類型的例項會透過指向實際原生句柄的指標表示,因為基礎資料類型的例項為可變長度。

Union

HIDL 不支援匿名聯集。除此之外,集合與 C 相似。

聯集不得包含修正類型 (例如指標、檔案描述符、Binder 物件)。不需要特殊欄位或關聯類型,只要使用 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
  }