1. 程式人生 > 其它 >OpenHarmony輕量系統服務管理|系統服務間呼叫之對外介面詳解

OpenHarmony輕量系統服務管理|系統服務間呼叫之對外介面詳解

前言

  本文是對Samgr部分中子主題IUnknown的總體概述,相關程式碼檔案位於distributedschedule_samgr_lite\samgr\source

IUnknown概述

  IUnknown是鴻蒙系統中非常重要的一個概念,作為服務或功能間互動的對外介面。當建立一個服務或功能時,需要為其繫結一個對外介面。其他的服務可以通過這個介面和它互動。下面給出IUnknown的基本結構,在開發新的對外介面時都必須繼承這個基類,它包含三個基本的函式指標,分別是查詢介面(QueryInterface)、增加引用(AddRef)、減少引用(Release)。

  

1 struct IUnknown {
2 //查詢IUnknown介面 3 int (*QueryInterface)(IUnknown *iUnknown, int version, void **target); 4 //新增引用計數 5 int (*AddRef)(IUnknown *iUnknown); 6 //釋放對IUnknown介面的引用 7 int (*Release)(IUnknown *iUnknown); 8 };

  通過呼叫QueryInterface函式,可以得到指定服務或功能的對外介面,使用這個介面就可以完成與服務的互動。AddRef和Release函式用來維護指定的介面物件,當它在程式中被其他地方使用時,它的引用數會加1,當使用者減少時,引用數也會相應的減少。通過引用數的值來決定是否需要回收它佔用的記憶體資源。

 

例項分析

  上面介紹了IUnknown是什麼,以及它有什麼。這裡通過一些簡單的例項分析一下它的定義、初始化、註冊和使用。

  鴻蒙的系統服務框架給我們提供了IUnknown基本的結構,只包含介面查詢操作和增加/減少引用操作。要實現複雜的互動功能,我們還需要基於自身業務的需求為IUnknown增加更多的操作。為了便於統一維護,鴻蒙規定所有的對外介面必須繼承IUnknown基類。我們知道C語言沒有類和繼承的概念,所以通過結構體代替類,巨集定義代替繼承。在IUnknown中定義了INHERIT_IUNKNOWN,用於繼承IUnknown基類的三個函式指標。下面展示如何開發自定義的對外介面。

  

1 typedef struct MyApi {
2     INHERIT_IUNKNOWN;    //繼承IUnknown基類
3     BOOL (*MyCall)(IUnknown *iUnknown, const char *buff);    //新增的函式指標,實現自己的業務功能
4 }MyApi;

  介紹了新的對外介面如何定義後,我們進一步介紹如何定義它的例項物件結構。在後續服務或功能間互動的過程中,都是通過對外介面的例項物件來完成。下面是例項物件的定義。巨集定義INHERIT_IUNKNOWNENTRY的作用也類似於繼承,用於給IUnknown新增版本和引用欄位資訊。所以例項物件MyApiIMPL本質上是對IUnknown的再一次封裝,並作為介面的例項。

1 typedef struct MyApiIMPL {
2     INHERIT_IUNKNOWNENTRY(MyApi);
3 } MyApiIMPL;

  下面將MyApiIMPL所有的巨集定義展開,我們直觀的看一下它的結構,可以更好的理解。

 1 struct MyApiIMPL{
 2     //INHERIT_IUNKNOWNENTRY增加的兩個欄位
 3     uint16 ver;                   
 4     int16 ref;                   
 5     //自定義MyApi中的函式指標,前三個繼承自IUnknown基類
 6     int (*QueryInterface)(IUnknown *iUnknown, int version, void **target);
 7     int (*AddRef)(IUnknown *iUnknown);
 8     int (*Release)(IUnknown *iUnknown);
 9     BOOL (*MyCall)(IUnknown *iUnknown, const char *buff);
10 }

  要想使用自定義的對外介面,還需要初始化介面例項。初始化的過程很簡單,就是給所有的欄位賦值,完善介面的生命週期函式。巨集定義DEFAULT_IUNKNOWN_ENTRY_BEGIN的作用就是為IUnknown的基本函式和欄位賦初始值,不包括自定義欄位。初始化程式碼示例如下。

1 static MyApiIMPL apiIMPL = {
2     DEFAULT_IUNKNOWN_ENTRY_BEGIN,
3         .MyCall= FunCall,    //自定義欄位
4     DEFAULT_IUNKNOWN_ENTRY_END,
5 };

  初始化完成後,將介面例項註冊到指定的服務或功能中,完成對外介面的繫結。介面註冊的前提是Samgr例項物件已存在且指定的服務或功能已註冊到Samgr中。在Samgr例項物件中有指定的介面註冊函式RegisterFeatureApi(),指定服務名和功能名後,該介面會與其繫結。

  當我們完成介面的定義、初始化、註冊後,我們要如何使用它呢?前面我們提到服務或功能間的互動都是通過各自的對外介面來完成的,我們可以通過GetFeatureApi()和QueryInterface()函式來獲取指定服務和功能的對外介面。然後呼叫介面中的函式,向服務和功能發起互動。獲取指定服務和功能的對外介面和呼叫示例如下:

 1 MyApi *myApi = NULL;
 2 //通過Samgr的獲取介面函式,查詢指定服務和功能對應的介面
 3 IUnknown *iUnknown = SAMGR_GetInstance()->GetFeatureApi(EXAMPLE_SERVICE, EXAMPLE_FEATURE);
 4 if (iUnknown == NULL) {
 5     return NULL;
 6 }
 7 //查詢指定版本的介面,轉化為MyApi,介面引用數+1
 8 int result = iUnknown->QueryInterface(iUnknown, DEFAULT_VERSION, (void **)&myApi);
 9 if (result != 0 || demoApi == NULL) {
10     return NULL;
11 }
12 //呼叫介面中的函式向服務發訊息
13 if (myApi->MyCall == NULL) {
14     return NULL;
15 }
16 myApi->MyCall((IUnknown *)myApi, "Hello World!");
17 //介面使用完後,減少引用數
18 myApi->Release((IUnknown *)myApi);

  上面講解的是一種通用的介面定義、初始化、註冊和使用的過程,實際上服務和功能間的互動還需要考慮是同進程內還是跨程序。在同進程內服務和功能的互動使用繼承自IUnknown的一對介面,在跨程序服務和功能的互動是通過繼承INHERIT_SERVER_IPROXY和INHERIT_CLIENT_IPROXY來完成的。它們的實現思想上是一致的,這裡就不額外討論了。

總結

  服務或功能間通過統一的介面物件來互動,可以減少程式碼的複雜性,並且不會將服務的內部細節暴露出來,封裝性更高。開發自定義的對外介面的過程及使用如下:

  自定義對外介面:繼承IUnknown基類,新增自定義的業務函式指標。

  獲取並初始化介面例項:封裝自定義的介面,新增欄位,併為函式指標等賦值。

  註冊/繫結介面例項:將自定義的介面例項繫結到指定的服務或功能上。

  呼叫介面例項:獲取指定服務或功能的對外介面,呼叫介面中的業務函式發起互動。