(六十八) HIDL C++ 軟件包&&接口

軟件包

注意:本部分使用 .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 示例文件可以在 samples 軟件包中創建一個 IFoo 接口:

package [email protected];
interface IFoo {
    struct Foo {
       int64_t someValue;
       handle  myHandle;
    };

    someMethod() generates (vec<uint32_t>);
    anotherMethod(Foo foo) generates (int32_t ret);
};

生成的文件

HIDL 軟件包中自動生成的文件會鏈接到與軟件包同名的單個共享庫(例如 [email protected])。該共享庫還會導出單個標頭 IFoo.h,用於包含在客戶端和服務器中。綁定式模式使用 hidl-gen 編譯器並以 IFoo.hal 接口文件作爲輸入,它具有以下自動生成的文件:





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 += [email protected]
在 Android.bp 中:
shared_libs: [
    /* ... */
    "[email protected]",
],
對於特定的庫:

libhidlbase    包含標準 HIDL 數據類型。除非您的接口只包含直接映射到 C++ 基元的基元,否則您還必須鏈接到此庫:

LOCAL_SHARED_LIBRARIES += libhidlbase
libhidltransport    通過不同的 RPC/IPC 機制處理 HIDL 調用的傳輸。您必須始終鏈接到此庫:

LOCAL_SHARED_LIBRARIES += libhidltransport
libhwbinder    您還必須鏈接到此庫:

LOCAL_SHARED_LIBRARIES += libhwbinder
libfmq    要使用快速消息隊列 IPC,您還必須鏈接到此庫。

LOCAL_SHARED_LIBRARIES += libfmq

命名空間

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 接口 [email protected]::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 [email protected];
interface IFooCallback {
    sendEvent(uint32_t event_id);
    sendData(hidl_vec<uint8_t> data);
}
IFoo 中採用 IFooCallback 參數的新方法示例:

package [email protected];
interface IFoo {
    struct Foo {
       int64_t some_value;
       Handle my_handle;
    };

    someMethod(Foo foo) generates (int32_t ret);
    another_method() generates (hidl_vec<uint32_t>);
    register_callback(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(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 : 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);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章