鸿蒙进阶——HDI及IDL硬件接口IPC框架小结
文章大纲
- 一、HDI 和IDL 概述
- 二、IPC框架
- 三、HDI 在不同类型系统上的架构
- 四、HDI接口基于C/S的IPC 实现
- 1、HDI 接口声明
- 2、编写 idl文件的构建编译脚本BUILD.gn
- 3、继承编译后的idl 接口实现HDI服务的真正业务功能
- 4、实现HDI服务驱动入口
- 5、发布HDI服务
- 6、调用HDI服务
一、HDI 和IDL 概述
HDI(Hardware Device Interface):OpenHarmony硬件设备接口,定义系统中跨进程通信的接口,实现服务间的跨进程通信,常见的HDI Service 有USB Service 他是由HDF间接启动的。而IDL(Interface Description Language)接口描述语言,是HDI接口的语言格式,接口定义使用IDL语言描述并以·idl文件形式保存。
二、IPC框架
HDI文件使用IDL语法描述HDI接口并保存为.idl文件,在编译过程中自动转换为C/C++语言的函数接口声明、客户端与服务端IPC相关过程代码,开发者只需要基于生成的ifoo.h函数接口实现具体服务功能即可。代码生成与编译功能已经集成在//drivers/hdf_core/adapter/uhdf2/hdi.gni编译模板,基于该编译模板编写idl文件的BUILD.gn就可以简单的生成客户端、服务端代码并编译为共享库。简而言之,就是开发者只需要根据IDL 语法描述接口,编译后就会自动生成一个C/S 结构的IPC通信功能块。
简而言之,HDI 是一套与硬件设备通信的PC 框架,而IDL 就是HDI文件使用的语法格式,HDI 框架经过编译后就自动生成一套C/S 架构的IPC 通信模块,而NNRT 就是通过HDI 接口与硬件设备芯片IPC通信。
三、HDI 在不同类型系统上的架构
在轻量级 OpenHarmony 系统上,出于减小系统性能负载考虑,HDI 实现为用户态共享库,由系统服务直接加载 HDI 实现到自己进程中函数调用使用。HDI 实现封装具体的用户态内核态交互过程,当需要访问驱动程序时使用 IO Service 请求将消息通过 system call 方式调用到内核驱动实现。而在标准 OpenHarmony 系统上,HDI 以独立服务进程方式部署,系统服务只加载 HDI 客户端实现到自己进程中,实际业务运行在独立进程中,客户端通过 IPC 与服务端交互,便于架构解耦、权限管理。
四、HDI接口基于C/S的IPC 实现
直通模式为函数实现方式,无论调用还是实现都不需要其他组件支持即可实现。
1、HDI 接口声明
HDI 的接口说明是通过IDL语法描述的.idl 文件,
package ohos.hdi.foo.v1_0;
import ohos.hdi.foo.v1_0.IFooCallback;
import ohos.hdi.foo.v1_0.MyTypes;
interface IFoo {
Ping([in] String sendMsg, [out] String recvMsg);
GetData([out] struct FooInfo info);
SendCallbackObj([in] IFooCallback cbObj);
}
如果interface中用到了自定义数据类型,将自定义类型定义到另一个MyTypes.idl文件中:
package ohos.hdi.foo.v1_0;
enum FooType {
FOO_TYPE_ONE = 1,
FOO_TYPE_TWO,
};
struct FooInfo {
unsigned int id;
String name;
enum FooType type;
};
如果需要从服务端回调,可以定义callback接口类IFooCallback.idl
package ohos.hdi.foo.v1_0;
[callback] interface IFooCallback {
PushData([in] String message);
}
2、编写 idl文件的构建编译脚本BUILD.gn
.idl 最终经过编译后就会自动生成对应IPC 模式的C/S端的通用的IPC代码框架。并且打包成共享库的形式,已经内置了相关的BUILD.gn 模版
import("//drivers/hdf_core/adapter/uhdf2/hdi.gni") # 编译idl必须要导入的模板
hdi("foo") { # 目标名称,会生成两个so,分别对应 libfoo_client_v1.0.z.so 和 libfoo_stub_v1.0.z.so
package = "ohos.hdi.foo.v1_0" # 包名,必须与idl路径匹配
module_name = "foo" # module_name控制dirver文件中驱动描 述符(struct HdfDriverEntry)的moduleName
sources = [ # 参与编译的idl文件
"IFoo.idl", # 接口idl
"IFooCallback.idl", # 用于回调的idl
"MyTypes.idl", # 自定义类型idl
]
language = "cpp" # 控制idl生成c或c++代码 可选择`c`或`cpp`
}
3、继承编译后的idl 接口实现HDI服务的真正业务功能
经过idl编译后将在out目录out/[product_name]/gen/drivers/interfaces/foo/v1_0生成通用的模型代码框架,比如说已经帮你完成了IPC的调用和监听逻辑,作为HDI服务开发者无需去考虑了,但是你这个服务真正的定制化的业务功能是需要自己去实现的,简而言之,实现这个HDI服务只是借助系统IPC帮你打通了通信链路,至于你要干什么需要你自己去实现。基于工具自动生成的foo_interface_service.h,实现其中的服务接口,并将相关源码编译为FooService.z.so则
但是因为驱动很多时候涉及到底层操作和多系统迁移的场景而使用C语言编写,所以驱动框架还提供了 HDI 服务的 C 语言实现的基础组件,C++ 实现则主要使用系统通信框架组件。
namespace OHOS {
namespace HDI {
namespace Foo {
namespace V1_0 {
class FooService : public IFoo { //继承接口类,并重写接口
public:
virtual ~FooService() {}
int32_t Ping(const std::string& sendMsg, std::string& recvMsg) override;
int32_t FooService::GetData(FooInfo& info) override;
int32_t FooService::SendCallbackObj(const sptr<IFooCallback>& cbObj) override;
};
} // namespace V1_0
} // namespace Foo
} // namespace Hdi
} // namespace OHOS
4、实现HDI服务驱动入口
HDI服务发布是基于用户态HDF驱动框架,所以需要实现一个驱动入口。驱动实现代码参考已经在out目录中生成,如out/gen/xxx/foo_interface_driver.cpp,可以根据业务需要直接使用该文件或参考该文件按业务需要重新实现。 然后将驱动入口源码编译为libfoo_driver.z.so(该名称无强制规定,与hcs配置中配套即可)。
由HDF 驱动框架实现。
5、发布HDI服务
在产品hcs配置中声明HDI服务,以标准系统Hi3516DV300单板为例,HDF设备配置路径为vendor/hisilicon/Hi3516DV300/hdf_config/uhdf/device_info.hcs,在其中新增以下配置:
fooHost :: host {
hostName = "fooHost";
priority = 50;
fooDevice :: device {
device0 :: deviceNode {
policy = 2;
priority = 100;
preload = 2;
moduleName = "libfoo_driver.z.so";
serviceName = "foo_service";
}
}
}
6、调用HDI服务
- 客户端在BUILD.gn中增加依赖: //drivers/interface/foo/v1.0:libfoo_proxy_1.0"
- 在代码中调用HDI接口(以CPP为例)
#include <v1_0/ifoo_interface.h>
int WorkFunc(void) {
sptr<IFoo> foo = OHOS::HDI::Foo::V1_0::Foo::Get(); // 使用Foo对象的内置静态方法获取该服务客户端实例
if (foo == nullptr) {
// hdi service not exist, handle error
}
foo->Ping(); // do interface call
}