資料類型

HIDL 資料宣告會產生 C++ 標準版面配置資料結構。這些結構體可放置在任何自然位置 (堆疊、檔案或全域範圍,或堆積),並可以相同方式組合。用戶端程式碼會呼叫 HIDL 代理程式程式碼,並傳入 const 參照和原始類型,而 Stub 和代理程式程式碼會隱藏序列化的詳細資料。

注意:開發人員編寫的程式碼無須明確序列化或反序列化資料結構。

下表將 HIDL 原始類型對應至 C++ 資料類型:

HIDL 類型 C++ 類型 Header/library
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

以下各節將詳細說明資料類型。

列舉

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 原始碼中顯示的順序,包含每個列舉器,從父項列舉器開始,一直到最後一個子項。舉例來說,這個程式碼會依序遞迴 WRITEREADNONECOMPARE。請參考上述 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() 函式,將 hidl_vec<T> 初始化為指向 T 類型外部資料緩衝區。

除了在產生的 C++ 標頭中適當地發出/插入結構體,使用 vec<T> 也會產生一些方便的函式,用於轉換 std::vector 和裸 T 指標。如果 vec<T> 用於參數,則使用該函式的函式會超載 (產生兩個原型),以便接受及傳遞該參數的 HIDL 結構體和 std::vector<T> 類型。

陣列

hidl 中的常數陣列由 libhidlbase 中的 hidl_array 類別表示。hidl_array<T, S1, S2, …, SN> 代表 N 維固定大小陣列 T[S1][S2]…[SN]

string

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 具有一致的記憶體版面配置。結構體可包含 HIDL 類型,包括 handlestringvec<T>,這些類型會指向不同的可變長度緩衝區。

帳號

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

handle 類型由 C++ 中的 hidl_handle 結構表示,這是一個簡單的包裝函式,用於指向 const native_handle_t 物件的指標 (這個類型在 Android 中已存在很長一段時間)。

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 指標的擁有權。它只是用來安全地儲存 native_handle_t 的指標,以便在 32 位元和 64 位元處理程序中使用。

hidl_handle 確實擁有其內含檔案描述符的情境包括:

  • 在呼叫 setTo(native_handle_t* handle, bool shouldOwn) 方法後,將 shouldOwn 參數設為 true
  • hidl_handle 物件是透過複製建構另一個 hidl_handle 物件而建立時
  • hidl_handle 物件從另一個 hidl_handle 物件複製指派

hidl_handle 提供隱含和明確的轉換作業,可從/至 native_handle_t* 物件轉換。HIDL 中 handle 類型的用途主要是透過 HIDL 介面傳遞檔案描述項。因此,單一檔案描述元會以 native_handle_t 表示,其中沒有 int 和單一 fd。如果用戶端和伺服器位於不同的程序中,RPC 實作會自動處理檔案描述符,確保兩個程序都能在同一檔案上運作。

雖然程序在 hidl_handle 中收到的檔案描述元在該程序中有效,但在接收函式之外不會保留 (在函式傳回時會關閉)。如要保留對檔案描述元檔案的持續存取權,程序必須 dup() 所封裝的檔案描述元,或複製整個 hidl_handle 物件。

記憶事項

HIDL memory 類型會對應至 libhidlbase 中的 hidl_memory 類別,代表未對應的共用記憶體。這是必須在程序之間傳遞的物件,才能在 HIDL 中共用記憶體。如要使用共用記憶體,請按照下列步驟操作:

  1. 取得 IAllocator 的例項 (目前僅提供「ashmem」例項),並使用該例項分配共用記憶體。
  2. IAllocator::allocate() 會傳回 hidl_memory 物件,可透過 HIDL RPC 傳遞,並使用 libhidlmemorymapMemory 函式對應至程序。
  3. mapMemory 會傳回 sp<IMemory> 物件的參照,可用於存取記憶體。(IMemoryIAllocator 是在 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
  }));

記憶體的實際變更必須透過 IMemory 物件完成,無論是在建立 mem 的一方,還是透過 HIDL RPC 接收 mem 的一方。

// 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<>