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

转载:https://source.android.com/devices/architecture/hidl-cpp/packages

软件包

注意:本部分使用 .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);

猜你喜欢

转载自blog.csdn.net/sinat_20059415/article/details/81028621