前言
本文是对Samgr部分中子主题IUnknown的总体概述,相关代码文件位于distributedschedule_samgr_lite\samgr\source
。对于IUnknown的分析思路是采用1篇总体概述+n篇代码标注的方式进行技术分享。在本文中提到的数据结构或函数的详细分析可以在文末的附录中找到,其中也包含了许多优秀代码的分析。
IUnknown概述
IUnknown是鸿蒙系统中非常重要的一个概念,作为服务或功能间交互的对外接口。当创建一个服务或功能时,需要为其绑定一个对外接口。其他的服务可以通过这个接口和它交互。下面给出IUnknown的基本结构,在开发新的对外接口时都必须继承这个基类,它包含三个基本的函数指针,分别是查询接口(QueryInterface)、增加引用(AddRef)、减少引用(Release)。
struct IUnknown {
//查询IUnknown接口
int (*QueryInterface)(IUnknown *iUnknown, int version, void **target);
//添加引用计数
int (*AddRef)(IUnknown *iUnknown);
//释放对IUnknown接口的引用
int (*Release)(IUnknown *iUnknown);
};
通过调用QueryInterface函数,可以得到指定服务或功能的对外接口,使用这个接口就可以完成与服务的交互。AddRef和Release函数用来维护指定的接口对象,当它在程序中被其他地方使用时,它的引用数会加1,当使用者减少时,引用数也会相应的减少。通过引用数的值来决定是否需要回收它占用的内存资源。
实例分析
上面介绍了IUnknown是什么,以及它有什么。这里通过一些简单的实例分析一下它的定义、初始化、注册和使用。
鸿蒙的系统服务框架给我们提供了IUnknown基本的结构,只包含接口查询操作和增加/减少引用操作。要实现复杂的交互功能,我们还需要基于自身业务的需求为IUnknown增加更多的操作。为了便于统一维护,鸿蒙规定所有的对外接口必须继承IUnknown基类。我们知道C语言没有类和继承的概念,所以通过结构体代替类,宏定义代替继承。在IUnknown中定义了INHERIT_IUNKNOWN,用于继承IUnknown基类的三个函数指针。下面展示如何开发自定义的对外接口。
typedef struct MyApi {
INHERIT_IUNKNOWN; //继承IUnknown基类
BOOL (*MyCall)(IUnknown *iUnknown, const char *buff); //新增的函数指针,实现自己的业务功能
}MyApi;
介绍了新的对外接口如何定义后,我们进一步介绍如何定义它的实例对象结构。在后续服务或功能间交互的过程中,都是通过对外接口的实例对象来完成。下面是实例对象的定义。宏定义INHERIT_IUNKNOWNENTRY的作用也类似于继承,用于给IUnknown添加版本和引用字段信息。所以实例对象MyApiIMPL本质上是对IUnknown的再一次封装,并作为接口的实例。
typedef struct MyApiIMPL {
INHERIT_IUNKNOWNENTRY(MyApi);
} MyApiIMPL;
下面将MyApiIMPL所有的宏定义展开,我们直观的看一下它的结构,可以更好的理解。
struct MyApiIMPL{
//INHERIT_IUNKNOWNENTRY增加的两个字段
uint16 ver;
int16 ref;
//自定义MyApi中的函数指针,前三个继承自IUnknown基类
int (*QueryInterface)(IUnknown *iUnknown, int version, void **target);
int (*AddRef)(IUnknown *iUnknown);
int (*Release)(IUnknown *iUnknown);
BOOL (*MyCall)(IUnknown *iUnknown, const char *buff);
}
要想使用自定义的对外接口,还需要初始化接口实例。初始化的过程很简单,就是给所有的字段赋值,完善接口的生命周期函数。宏定义DEFAULT_IUNKNOWN_ENTRY_BEGIN的作用就是为IUnknown的基本函数和字段赋初始值,不包括自定义字段。初始化代码示例如下。
static MyApiIMPL apiIMPL = {
DEFAULT_IUNKNOWN_ENTRY_BEGIN,
.MyCall= FunCall, //自定义字段
DEFAULT_IUNKNOWN_ENTRY_END,
};
初始化完成后,将接口实例注册到指定的服务或功能中,完成对外接口的绑定。接口注册的前提是Samgr实例对象已存在且指定的服务或功能已注册到Samgr中。在Samgr实例对象中有指定的接口注册函数RegisterFeatureApi(),指定服务名和功能名后,该接口会与其绑定。 当我们完成接口的定义、初始化、注册后,我们要如何使用它呢?前面我们提到服务或功能间的交互都是通过各自的对外接口来完成的,我们可以通过**GetFeatureApi()和QueryInterface()**函数来获取指定服务和功能的对外接口。然后调用接口中的函数,向服务和功能发起交互。获取指定服务和功能的对外接口和调用示例如下。
MyApi *myApi = NULL;
//通过Samgr的获取接口函数,查询指定服务和功能对应的接口
IUnknown *iUnknown = SAMGR_GetInstance()->GetFeatureApi(EXAMPLE_SERVICE, EXAMPLE_FEATURE);
if (iUnknown == NULL) {
return NULL;
}
//查询指定版本的接口,转化为MyApi,接口引用数+1
int result = iUnknown->QueryInterface(iUnknown, DEFAULT_VERSION, (void **)&myApi);
if (result != 0 || demoApi == NULL) {
return NULL;
}
//调用接口中的函数向服务发消息
if (myApi->MyCall == NULL) {
return NULL;
}
myApi->MyCall((IUnknown *)myApi, "Hello World!");
//接口使用完后,减少引用数
myApi->Release((IUnknown *)myApi);
上面讲解的是一种通用的接口定义、初始化、注册和使用的过程,实际上服务和功能间的交互还需要考虑是同进程内还是跨进程。在同进程内服务和功能的交互使用继承自IUnknown的一对接口,在跨进程服务和功能的交互是通过继承INHERIT_SERVER_IPROXY和INHERIT_CLIENT_IPROXY来完成的。它们的实现思想上是一致的,这里就不额外讨论了。
总结
服务或功能间通过统一的接口对象来交互,可以减少代码的复杂性,并且不会将服务的内部细节暴露出来,封装性更高。开发自定义的对外接口的过程及使用如下:
自定义对外接口: 继承IUnknown基类,新增自定义的业务函数指针。
获取并初始化接口实例: 封装自定义的接口,新增字段,并为函数指针等赋值。
注册/绑定接口实例: 将自定义的接口实例绑定到指定的服务或功能上。
调用接口实例: 获取指定服务或功能的对外接口,调用接口中的业务函数发起交互。