HIDL C++
Android O 對 Android 操作系統的架構重新進行了設計,以在獨立於設備的 Android 平台與特定於設備和供應商的代碼之間定義清晰的接口。Android 已經以 HAL 接口的形式(在 hardware/libhardware
中定義為 C 標頭)定義了許多此類接口。HIDL 將這些 HAL 接口替換為穩定的帶版本接口,它們可以是采用 C++(如下所述)或 Java 的客戶端和服務器端 HIDL 接口。
本部分中的幾頁內容介紹了 HIDL 接口的 C++ 實現,其中詳細說明了 hidl-gen
編譯器基於 HIDL .hal
文件自動生成的文件,這些文件如何打包,以及如何將這些文件與使用它們的 C++ 代碼集成。
客戶端和服務器實現
HIDL 接口具有客戶端和服務器實現:
- HIDL 接口的客戶端實現是指通過在該接口上調用方法來使用該接口的代碼。
- 服務器實現是指 HIDL 接口的實現,它可接收來自客戶端的調用並返回結果(如有必要)。
在從 libhardware
HAL 轉換為 HIDL HAL 的過程中,HAL 實現成為服務器,而調用 HAL 的進程則成為客戶端。默認實現可提供直通和綁定式 HAL,並可能會隨着時間而發生變化:
圖 1. 舊版 HAL 的發展歷程。
創建 HAL 客戶端
首先將 HAL 庫添加到 makefile 中:
- Make:
LOCAL_SHARED_LIBRARIES += android.hardware.nfc@1.0
- Soong:
shared_libs: [ …, android.hardware.nfc@1.0 ]
接下來,添加 HAL 頭文件:
#include <android/hardware/nfc/1.0/IFoo.h>
…
// in code:
sp<IFoo> client = IFoo::getService();
client->doThing();
創建 HAL 服務器
要創建 HAL 實現,您必須具有表示 HAL 的 .hal
文件並已在 hidl-gen
上使用 -Lmakefile
或 -Landroidbp
為 HAL 生成 makefile(./hardware/interfaces/update-makefiles.sh
會為內部 HAL 文件執行這項操作,這是一個很好的參考)。從 libhardware
通過 HAL 傳輸時,您可以使用 c2hal 輕松完成許多此類工作。
要創建必要的文件來實現您的 HAL,請使用以下代碼:
PACKAGE=android.hardware.nfc@1.0
LOC=hardware/interfaces/nfc/1.0/default/
m -j hidl-gen
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces \
-randroid.hidl:system/libhidl/transport $PACKAGE
hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces \
-randroid.hidl:system/libhidl/transport $PACKAGE
為了讓 HAL 在直通模式下發揮作用(對於舊版設備),必須具有 HIDL_FETCH_IModuleName 函數(位於 /(system|vendor|...)/lib(64)?/hw/android.hardware.package@3.0-impl($OPTIONAL_IDENTIFIER).so
下),其中 $OPTIONAL_IDENTIFIER
是一個標識直通實現的字符串。直通模式要求會通過上述命令自動滿足,這些命令也會創建 android.hardware.nfc@1.0-impl
目標,但是可以使用任何擴展。例如,android.hardware.nfc@1.0-impl-foo
就是使用 -foo
區分自身。
接下來,使用相應功能填寫存根並設置守護進程。守護進程代碼(支持直通)示例:
#include <hidl/LegacySupport.h>
int main(int /* argc */, char* /* argv */ []) {
return defaultPassthroughServiceImplementation<INfc>("nfc");
}
defaultPassthroughServiceImplementation
將對提供的 -impl
庫執行 dlopen()
操作,並將其作為綁定式服務提供。守護進程代碼(對於純綁定式服務)示例:
int main(int /* argc */, char* /* argv */ []) {
// This function must be called before you join to ensure the proper
// number of threads are created. The threadpool will never exceed
// size one because of this call.
::android::hardware::configureRpcThreadpool(1 /*threads*/, true /*willJoin*/);
sp nfc = new Nfc();
const status_t status = nfc->registerAsService();
if (status != ::android::OK) {
return 1; // or handle error
}
// Adds this thread to the threadpool, resulting in one total
// thread in the threadpool. We could also do other things, but
// would have to specify 'false' to willJoin in configureRpcThreadpool.
::android::hardware::joinRpcThreadpool();
return 1; // joinRpcThreadpool should never return
}
此守護進程通常存在於 $PACKAGE + "-service-suffix"
(例如 android.hardware.nfc@1.0-service
)中,但也可以位於任何位置。HAL 的特定類的 sepolicy 是屬性 hal_<module>
(例如 hal_nfc)
)。您必須將此屬性應用到運行特定 HAL 的守護進程(如果同一進程提供多個 HAL,則可以將多個屬性應用到該進程)。
軟件包
注意:本部分使用 .hal
示例文件來說明 HIDL 語言結構如何映射到 C++。
HIDL 接口軟件包位於 hardware/interfaces
或 vendor/
目錄下(少數例外情況除外)。hardware/interfaces
頂層會直接映射到 android.hardware
軟件包命名空間;版本是軟件包(而不是接口)命名空間下的子目錄。
hidl-gen
編譯器會將 .hal
文件編譯成一組 .h
和 .cpp
文件。這些自動生成的文件可用來編譯客戶端/服務器實現鏈接到的共享庫。用於編譯此共享庫的 Android.bp
文件由 hardware/interfaces/update-makefiles.sh
腳本自動生成。每次將新軟件包添加到 hardware/interfaces
或在現有軟件包中添加/移除 .hal
文件時,您都必須重新運行該腳本,以確保生成的共享庫是最新的。
例如,IFoo.hal
示例文件應該位於 hardware/interfaces/samples/1.0
下。IFoo.hal
示例文件會在示例軟件包中創建一個 IFoo 接口:
package android.hardware.samples@1.0;
interface IFoo {
struct Foo {
int64_t someValue;
handle myHandle;
};
someMethod() generates (vec<uint32_t>);
anotherMethod(Foo foo) generates (int32_t ret);
};
生成的文件
HIDL 軟件包中自動生成的文件會鏈接到與軟件包同名的單個共享庫(例如 android.hardware.samples@1.0
)。該共享庫還會導出單個標頭 IFoo.h
,用於包含在客戶端和服務器中。綁定式模式使用 hidl-gen
編譯器並以 IFoo.hal
接口文件作為輸入,它具有以下自動生成的文件:
圖 1. 由編譯器生成的文件。
IFoo.h
。描述 C++ 類中的純IFoo
接口;它包含IFoo.hal
文件中的IFoo
接口中所定義的方法和類型,必要時會轉換為 C++ 類型。不包含與用於實現此接口的 RPC 機制(例如HwBinder
)相關的詳細信息。類的命名空間包含軟件包名稱和版本號,例如::android::hardware::samples::IFoo::V1_0
。客戶端和服務器都包含此標頭:客戶端用它來調用方法,服務器用它來實現這些方法。IHwFoo.h
。頭文件,其中包含用於對接口中使用的數據類型進行序列化的函數的聲明。開發者不得直接包含其標頭(它不包含任何類)。BpFoo.h
。從IFoo
繼承的類,可描述接口的HwBinder
代理(客戶端)實現。開發者不得直接引用此類。BnFoo.h
。保存對IFoo
實現的引用的類,可描述接口的HwBinder
存根(服務器端)實現。開發者不得直接引用此類。FooAll.cpp
。包含HwBinder
代理和HwBinder
存根的實現的類。當客戶端調用接口方法時,代理會自動從客戶端封送參數,並將事務發送到綁定內核驅動程序,該內核驅動程序會將事務傳送到另一端的存根(該存根隨后會調用實際的服務器實現)。
這些文件的結構類似於由 aidl-cpp
生成的文件(如需了解詳細信息,請參閱 HIDL 概覽中的“直通模式”)。獨立於 HIDL 使用的 RPC 機制的唯一一個自動生成的文件是 IFoo.h
;其他所有文件都與 HIDL 使用的 HwBinder RPC 機制相關聯。因此,客戶端和服務器實現不得直接引用除 IFoo
之外的任何內容。為了滿足這項要求,請只包含 IFoo.h
並鏈接到生成的共享庫。
注意:HwBinder 只是一種可能的傳輸機制,未來可能會添加新的傳輸機制。
鏈接到共享庫
使用軟件包中的任何接口的客戶端或服務器必須在下面的其中一 (1) 個位置包含該軟件包的共享庫:
- 在 Android.mk 中:
LOCAL_SHARED_LIBRARIES += android.hardware.samples@1.0
- 在 Android.bp 中:
shared_libs: [
/* ... */
"android.hardware.samples@1.0",
],
您可能需要包含的其他庫:
libhidlbase |
包含標准 HIDL 數據類型。從 Android 10 開始,該庫還包含先前在 libhidltransport 和 libhwbinder 中的所有符號。 |
---|---|
libhidltransport |
通過不同的 RPC/IPC 機制處理 HIDL 調用的傳輸。Android 10 棄用了該庫。 |
libhwbinder |
特定於 Binder 的符號。Android 10 棄用了該庫。 |
libfmq |
快速消息隊列 IPC。 |
命名空間
HIDL 函數和類型(如 Return<T>
和 Void()
)已在命名空間 ::android::hardware
中進行聲明。軟件包的 C++ 命名空間由軟件包的名稱和版本號確定。例如,hardware/interfaces
下版本為 1.2 的軟件包 mypackage具有以下特質:
- C++ 命名空間是
::android::hardware::mypackage::V1_2
- 該軟件包中
IMyInterface
的完全限定名稱是:::android::hardware::mypackage::V1_2::IMyInterface
。(IMyInterface
是一個標識符,而不是命名空間的一部分)。 - 在軟件包的
types.hal
文件中定義的類型標識為:::android::hardware::mypackage::V1_2::MyPackageType
接口
HIDL 軟件包中定義的每個接口在其軟件包的命名空間內都有自己的自動生成的 C++ 類。客戶端和服務器會通過不同的方式處理接口:
- 服務器實現接口。
- 客戶端在接口上調用方法。
接口可以由服務器按名稱注冊,也可以作為參數傳遞到以 HIDL 定義的方法。例如,框架代碼可設定一個從 HAL 接收異步消息的接口,並將該接口直接傳遞到 HAL(無需注冊該接口)。
服務器實現
實現 IFoo
接口的服務器必須包含自動生成的 IFoo
頭文件:
#include <android/hardware/samples/1.0/IFoo.h>
該標頭由 IFoo
接口的共享庫自動導出以進行鏈接。IFoo.hal
示例:
// IFoo.hal
interface IFoo {
someMethod() generates (vec<uint32_t>);
...
}
IFoo 接口的服務器實現的示例框架:
// From the IFoo.h header
using android::hardware::samples::V1_0::IFoo;
class FooImpl : public IFoo {
Return<void> someMethod(foo my_foo, someMethod_cb _cb) {
vec<uint32_t> return_data;
// Compute return_data
_cb(return_data);
return Void();
}
...
};
要使服務器接口的實現可供客戶端使用,您可以:
- 向
hwservicemanager
注冊接口實現(詳情見下文),
或 - 將接口實現作為接口方法的參數進行傳遞(詳情見異步回調部分)。
注冊接口實現時,hwservicemanager
進程會按名稱和版本號跟蹤設備上正在運行的已注冊 HIDL 接口。服務器可以按名稱注冊 HIDL 接口實現,而客戶端則可以按名稱和版本號請求服務實現。該進程可提供 HIDL 接口 android.hidl.manager@1.0::IServiceManager
。
每個自動生成的 HIDL 接口頭文件(如 IFoo.h
)都有一個 registerAsService()
方法,可用於向 hwservicemanager
注冊接口實現。唯一一個必需的參數是接口實現的名稱,因為稍后客戶端將使用此名稱從 hwservicemanager
檢索接口:
::android::sp<IFoo> myFoo = new FooImpl();
::android::sp<IFoo> mySecondFoo = new FooAnotherImpl();
status_t status = myFoo->registerAsService();
status_t anotherStatus = mySecondFoo->registerAsService("another_foo");
hwservicemanager
會將 [package@version::interface, instance_name]
組合視為唯一,以使不同的接口(或同一接口的不同版本)能夠采用完全相同的實例名稱無沖突地注冊。如果您調用的 registerAsService()
具有完全相同的軟件包版本、接口和實例名稱,則 hwservicemanager
將丟棄對先前注冊的服務的引用,並使用新的服務。
客戶端實現
和服務器一樣,客戶端也必須 #include
其引用的每個接口:
#include <android/hardware/samples/1.0/IFoo.h>
客戶端可以通過兩種方式獲取接口:
- 通過
I<InterfaceName>::getService
(借助hwservicemanager
) - 通過接口方法
每個自動生成的接口頭文件都有一個靜態 getService
方法,可用於從 hwservicemanager
檢索服務實例:
// getService will return nullptr if the service can't be found
sp<IFoo> myFoo = IFoo::getService();
sp<IFoo> myAlternateFoo = IFoo::getService("another_foo");
現在,客戶端有一個 IFoo
接口,並可以向其(將其當作本地類實現)調用方法。實際上,實現可以在同一個進程中運行,也可以在不同的進程中運行,甚至還可以在另一個設備上運行(通過 HAL 遠程處理)。由於客戶端在 1.0
版軟件包中包含的 IFoo
對象上調用 getService
,因此僅當服務器實現與 1.0
客戶端兼容時,hwservicemanager
才會返回該實現。實際上,這意味着系統只會返回版本為 1.n
的服務器實現(x.(y+1)
版本的接口必須擴展(繼承自)x.y
)。
此外,您也可以使用 castFrom
方法,在不同的接口之間進行類型轉換。該方法會通過以下方式發揮作用:對遠程接口進行 IPC 調用,以確保底層類型與正在請求的類型相同。如果請求的類型不可用,則返回 nullptr
。
sp<V1_0::IFoo> foo1_0 = V1_0::IFoo::getService();
sp<V1_1::IFoo> foo1_1 = V1_1::IFoo::castFrom(foo1_0);
異步回調
很多現有的 HAL 實現會與異步硬件通信,這意味着它們需要以異步方式通知客戶端已發生的新事件。HIDL 接口可以用作異步回調,因為 HIDL 接口函數可以將 HIDL 接口對象用作參數。
IFooCallback.hal
接口文件示例:
package android.hardware.samples@1.0;
interface IFooCallback {
sendEvent(uint32_t event_id);
sendData(vec<uint8_t> data);
}
IFoo
中采用 IFooCallback
參數的新方法示例:
package android.hardware.samples@1.0;
interface IFoo {
struct Foo {
int64_t someValue;
handle myHandle;
};
someMethod(Foo foo) generates (int32_t ret);
anotherMethod() generates (vec<uint32_t>);
registerCallback(IFooCallback callback);
};
使用 IFoo
接口的客戶端是 IFooCallback
接口的服務器;它會提供 IFooCallback
的實現:
class FooCallback : public IFooCallback {
Return<void> sendEvent(uint32_t event_id) {
// process the event from the HAL
}
Return<void> sendData(const hidl_vec<uint8_t>& data) {
// process data from the HAL
}
};
它也可以簡單地通過 IFoo
接口的現有實例來傳遞該實現:
sp<IFooCallback> myFooCallback = new FooCallback();
myFoo.registerCallback(myFooCallback);
實現 IFoo
的服務器會將此作為 sp<IFooCallback>
對象進行接收。它可以存儲該回調,而且只要它想使用此接口,均可回調到客戶端。
服務終止通知接收方
由於服務實現可以在不同的進程中運行,因此可能會出現實現接口的進程已終止但客戶端仍保持活動狀態的情況。對托管在已終止進程中的接口對象的任何調用都將失敗,並會返回相應的傳輸錯誤(isOK()
將返回 false)。要從這類故障中恢復正常,唯一的方法是通過調用 I<InterfaceName>::getService()
來請求服務的新實例。僅當崩潰的進程已重新啟動且已向 servicemanager
重新注冊其服務時,這種方法才有效(對 HAL 實現而言通常如此)。
接口的客戶端也可以注冊為服務終止通知接收方,以便在服務終止時收到通知,而不是被動地應對這種情況。要在檢索的 IFoo
接口上注冊此類通知,客戶端可以執行以下操作:
foo->linkToDeath(recipient, 1481 /* cookie */);
recipient
參數必須是由 HIDL 提供的 android::hardware::hidl_death_recipient
接口的實現,該接口中包含在托管接口的進程終止時從 RPC 線程池中的線程調用的單個 serviceDied()
方法:
class MyDeathRecipient : public android::hardware::hidl_death_recipient {
virtual void serviceDied(uint64_t cookie, const android::wp<::android::hidl::base::V1_0::IBase>& who) {
// Deal with the fact that the service died
}
}
cookie
參數包含通過 linkToDeath()
傳入的 Cookie,而 who
參數則包含一個弱指針,它指向表示客戶端中的服務的對象。在上面給出的調用示例中,cookie
等於 1481,who
等於 foo
。
您也可以在注冊服務終止通知接收方后將其取消注冊:
foo->unlinkToDeath(recipient);
數據類型
HIDL 數據聲明可生成 C++ 標准布局數據結構。您可以將這些結構放置在任何合適的位置(可以放在堆棧上,放在文件或全局范圍內,也可以放在堆區上),而且這些結構能以相同的方式構成。客戶端代碼會調用傳入常量引用和基元類型的 HIDL 代理代碼,而存根和代理代碼會隱藏序列化的細節。
注意:在任何情況下,開發者編寫的代碼都不需要對數據結構進行顯式序列化或反序列化。
下表說明 HIDL 基元與 C++ 數據類型之間的對應關系:
HIDL 類型 | C++ 類型 | 頭文件/庫 |
---|---|---|
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 源代碼中顯示的順序迭代),從父級枚舉開始到最后一個子級。例如,此代碼按這樣的順序迭代 WRITE
、READ
、NONE
和 COMPARE
。根據上述 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]
。
字符串
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 字符串還有轉換構造函數,因此 C 字符串 (char *
) 和 C++ 字符串 (std::string
) 可用於采用 HIDL 字符串的方法。
結構
HIDL 形式的 struct
只能包含固定大小的數據類型,不能包含任何函數。HIDL 結構定義會直接映射到 C++ 形式的標准布局 struct
,從而確保 struct
具有一致的內存布局。一個結構可以包括多種指向單獨的可變長度緩沖區的 HIDL 類型(包括 handle
、string
和 vec<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 接口傳遞文件描述符。因此,單個文件描述符由沒有 int
的 native_handle_t
和單個 fd
表示。如果客戶端和服務器在不同的進程中運行,則 RPC 實現將自動處理文件描述符,以確保這兩個進程可對同一個文件執行操作。
盡管由某個進程在 hidl_handle
中接收的文件描述符在該進程中有效,但超出接收函數范圍后該描述符將不會持續存在(該函數返回后描述符將會關閉)。要持續訪問文件描述符,進程必須對所封裝的文件描述符執行 dup()
操作或復制整個 hidl_handle
對象。
內存
HIDL memory
類型會映射到 libhidlbase
中的 hidl_memory
類,該類表示未映射的共享內存。這是要在 HIDL 中共享內存而必須在進程之間傳遞的對象。要使用共享內存,需滿足以下條件:
- 獲取
IAllocator
的實例(當前只有“ashmem”實例可用)並使用該實例分配共享內存。 IAllocator::allocate()
返回hidl_memory
對象,該對象可通過 HIDL RPC 傳遞,並能使用libhidlmemory
的mapMemory
函數映射到某個進程。mapMemory
返回對可用於訪問內存的sp<IMemory>
對象的引用(IMemory
和IAllocator
在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 接收更改的一端完成)。
// 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();
接口
接口可作為對象傳遞。“接口”一詞可用作 android.hidl.base@1.0::IBase
類型的語法糖;此外,當前的接口以及任何導入的接口都將被定義為一個類型。
存儲接口的變量應該是強指針:sp<IName>
。接受接口參數的 HIDL 函數會將原始指針轉換為強指針,從而導致不可預料的行為(可能會意外清除指針)。為避免出現問題,請務必將 HIDL 接口存儲為 sp<>
。
函數
HIDL 接口中的函數會映射到自動生成的 IFoo
C++ 類聲明中的方法。每個函數的名稱在 C++ 中都將保持不變;下面幾部分介紹了 HIDL 參數和返回值如何轉換為 C++。
函數參數
.hal
文件中列出的參數會映射到 C++ 數據類型。未映射到基元 C++ 類型的參數會通過常量引用進行傳遞。
對於具有返回值(具有 generates
語句)的每個 HIDL 函數,該函數的 C++ 參數列表中都有一個附加參數:使用 HIDL 函數的返回值調用的回調函數。有一種情況例外:如果 generates
子句包含直接映射到 C++ 基元的單個參數,則使用回調省略(回調會被移除,而返回值則會通過正常的 return
語句從函數返回)。
函數返回值
以下函數具有返回值。
傳輸錯誤和返回類型
generates
語句可以產生三種類型的函數簽名:
- 如果只有一個作為 C++ 基元的返回值,
generates
返回值會由Return<T>
對象中函數的值返回。 - 如果情況更復雜,
generates
返回值則會通過隨函數調用本身一起提供的回調參數返回,而函數則返回Return<void>
。 - 如果不存在
generates
語句,函數則會返回Return<void>
。
RPC 調用可能偶爾會遇到傳輸錯誤,例如服務器終止,傳輸資源不足以完成調用,或傳遞的參數不允許完成調用(例如缺少必需的回調函數)。Return
對象會存儲傳輸錯誤指示以及 T
值(Return<void>
除外)。
由於客戶端和服務器端函數具有相同的簽名,因此服務器端函數必須返回 Return
類型(即使其實現並不會指出傳輸錯誤)。Return<T>
對象會使用 Return(myTValue)
進行構建(也可以通過 mTValue
隱式構建,例如在 return
語句中),而 Return<void>
對象則使用 Void()
進行構建。
Return<T>
對象可以從其 T
值執行隱式轉換,也可以執行到該值的隱式轉換。您可以檢查 Return
對象是否存在傳輸錯誤,只需調用其 isOk()
方法即可。這項檢查不是必需的;不過,如果發生了一個錯誤,而您未在 Return
對象銷毀前對該錯誤進行檢查,或嘗試進行了 T
值轉換,則客戶端進程將會終止並記錄一個錯誤。如果 isOk()
表明存在由開發者代碼中的邏輯錯誤(例如將 nullptr
作為同步回調進行傳遞)導致的傳輸錯誤或失敗調用,則可以對 Return 對象調用 description()
以返回適合日志記錄的字符串。在這種情況下,您無法確定因調用失敗而在服務器上執行的代碼可能有多少。另外,您還可以使用 isDeadObject()
方法。此方法表明,之所以會顯示 !isOk()
,是因為遠程對象已崩潰或已不存在。isDeadObject()
一律表示 !isOk()
。
由值返回
如果 generates
語句映射到單個 C++ 基元,則參數列表中不會有任何回調參數,而實現會在 Return<T>
對象中提供返回值 T
,該值可以從基元類型 T
隱式生成。例如:
Return<uint32_t> someMethod() {
uint32_t return_data = ...; // Compute return_data
return return_data;
};
另外,您還可以使用 Return<*>::withDefault
方法。此方法會在返回值為 !isOk()
的情況下提供一個值。此方法還會自動將返回對象標記為正常,以免客戶端進程遭到終止。
使用回調參數返回
回調可以將 HIDL 函數的返回值回傳給調用方。回調的原型是 std::function
對象,其參數(從 generates
語句中獲取)會映射到 C++ 類型。它的返回值為 void(回調本身並不會返回任何值)。
具有回調參數的 C++ 函數的返回值具有 Return<void>
類型。服務器實現僅負責提供返回值。由於返回值已使用回調傳輸,因此 T
模板參數為 void
:
Return<void> someMethod(someMethod_cb _cb);
服務器實現應從其 C++ 實現中返回 Void()
(這是一個可返回 Return<void>
對象的靜態內嵌函數)。具有回調參數的典型服務器方法實現示例:
Return<void> someMethod(someMethod_cb _cb) {
// Do some processing, then call callback with return data
hidl_vec<uint32_t> vec = ...
_cb(vec);
return Void();
};
沒有返回值的函數
沒有 generates
語句的函數的 C++ 簽名將不會在參數列表中有任何回調參數。它的返回類型將為 Return<void>.
單向函數
以 oneway
關鍵字標記的函數是異步函數(其執行不會阻塞客戶端),而且沒有任何返回值。oneway
函數的 C++ 簽名將不會在參數列表中有任何回調參數,而且其 C++ 返回值將為 Return<void>
。