構建自己的C/C++插件開發框架zz
——初步設想
最近一直在學習OSGI方面的知識。買了一本《OSGI原理和最佳實踐》,可是還沒有到。遺憾的是,OSGI目前的幾個開源框架只支持Java,對C和C++都不支持的。可惜我們公司目前主要的開發語言還是c和c++,即便是引進OSGI,所得的好處範圍有限。而我對鬆散耦合的模塊化開發嚮往已久。查了一下OSGI對C++支持的好像是有一個開源項目,不過好像應用範圍很小。而SCA標準中是有對C++實現模型的支持的,但是幾個開源的框架目前還只支持JAVA。
昨天看了丁亮的轉載的一篇博客《C/C++:構建你自己的插件框架 》,原文的鏈接:http://blog.chinaunix.net/u/12783/showart_662937.html 。看了一下里面講的方法,自己倒是可以實現。所以有了構建自己的c/c++插件開發框架的想法。今天先寫一下初步的設想。
C/C++插件開發框架的要素
BlueDavy有一篇介紹服務框架要素的文章(鏈接:http://www.blogjava.net/BlueDavy/archive/2009/08/28/172259.html )。我的插件框架也要考慮、解決以下的幾個問題:
1、如何註冊插件;
2、如何調用插件;
3、如何測試插件;
4、插件的生命週期管理;
5、插件的管理和維護;
6、插件的組裝;
7、插件的出錯處理;
8、服務事件的廣播和訂閱(這個目前還沒有考慮要支持);
其中有幾個點很重要:1)插件框架要能夠使模塊鬆散耦合,做到真正的面向接口編程;2)框架要支持自動化測試:包括單元測試,集成測試;3)簡化部署;4)支持分佈式,模塊可以調用框架外的插件。
採用的技術
插件框架要解決的一個問題就是插件的動態加載能力。這裏可以使用共享庫的動態加載技術。當然,爲了簡單,第一步只考慮做一個linux下的插件框架。
總體結構
框架的總體結構上,參考OSGI的“微內核+系統插件+應用插件”結構。這裏要好好考慮一下把什麼做在內核中。關於微內核結構,以前我做個一個微內核流程引擎,會在後面有時間和大家分享。
框架中模塊間的數據傳送,有兩種解決方法:一是普元採用的XML數據總線的做法。優點是擴展性好,可讀性好。但是速度有些慢。二是採用我熟悉的信元流。優點的效率高,訪問方便,但是可讀性差一點,另外跨框架的數據傳送,需要考慮網絡字節序的問題。
對於框架間的通信,通過系統插件封裝,對應用插件隱藏通信細節。
部署
努力做到一鍵式部署。
——總體功能
在這一系列的上一個文章中,介紹了構建C/C++插件開發框架的初步設想,下面我會一步步的向下展開,來實現我的這個設想。
今天主要談一下我對這個框架的功能認識,或是期望。昨天看了一篇關於持續集成能力成熟度模型 的一篇文章,受此啓發,我對此框架的認識漸漸清晰。
這個框架可以當做我們公司底層產品(交換機,資源服務器等)的基礎設施。上層基於java開發的產品可以直接在OSGI上開發。
核心功能:
1、最重要的一個功能是,提供一個模塊化的編程模型,促進模塊化軟件開發,真正的實現針對接口編程。
2、提供一個有助於提高模塊可重用性的基礎設施。
3、提供一個C/C++插件的運行環境。
4、提供一個動態插件框架,插件可以動態更改,而無需重啓系統。這個功能雖然不難實現,但是用處好像不是很大。
--------------------------------------------------------------------------------
擴展部分功能:
1、支持分佈式系統結構,多個運行框架組合起來形成一個系統,對模塊內部隱藏遠程通訊細節。
2、支持系統的分層架構。
3、能夠和其他的開發框架進行集成,比如OSGI,SCA等。
4、多個運行框架中,能夠實現對運行框架的有效管理。
5、概念上要實現類似於SCA中component(構件),composite(組合構件),Domain(域)的概念。
--------------------------------------------------------------------------------
開發部分功能:
1、爲了簡化開發,開發一個Eclipse插件,用於開發框架中的C/C++插件。能夠根據插件開發嚮導,最終生成符合插件規範的公共代碼,配置文件,Makefile文件等。
--------------------------------------------------------------------------------
調試部分功能:
1、提供一個統一的日誌處理函數,可以集成Log4cpp。
2、提供模塊間的消息日誌,以及框架對外的接口日誌。
3、提供消息和日誌的追蹤功能,能將和某事件相關的消息和日誌單獨提取出來。
4、提供資源監測功能,監測對資源(內存,套接字,文件句柄等)的使用情況。
--------------------------------------------------------------------------------
測試部分功能:
1、集成一些單元測試框架,比如unitcpp,達到自動化單元測試的目標。
2、自己實現自動化集成測試框架,並且開發相應的Eclipse插件,簡化集成測試(利用腳本和信元流)。
3、集成原有的自動化功能測試框架flowtest,並且開發相應的Eclipse插件,簡化功能測試。
4、實現性能測試,監測框架。
--------------------------------------------------------------------------------
部署部分功能:
1、實現自動化部署。特別是在分佈式應用的情況下。
2、提供一個命令行程序,通過命令更改系統配置,管理插件。
——總體結構
這幾天爲了設計插件開發框架,嘗試用了一下發散思維來思考問題。中間看過依賴注入,AOP(面向方面編程),以及契約式設計等。雖然有些工具無法直接使用,但是這些思想還是可以借鑑的,比如依賴注入,契約式設計。至於AOP,和工具相關性較大,雖然思想不錯,但是無法直接在C++中使用。
我設計的插件間的依賴不是通過接口實現的,而是通過插件間的數據(信元流)。而信元流的檢測可以使用契約來檢查。
插件開發框架的總體結構
微內核 :
1、 負責插件的加載,檢測,初始化。
2、 負責服務的註冊。
3、 負責服務的調用。
4、 服務的管理。
擴展層:
1、 日誌的打印。
2、 消息(信元流)的解釋,將二進制格式解釋爲文本。便於定位。
3、 消息和日誌的追蹤。
分佈式處理層:
1、 用於和其他的框架通信。
2、 和其他的框架搭配,形成一個分佈式的系統。
自動化測試框架層:
1、 集成 cppunit 。
2、 自動化集成測試框架。
3、 自動化功能測試框架。
和第三方框架集成層:
1 、和 第三方框架 集成層。
——核心層設計和實現
上面一篇文章大致描述了一下插件開發框架整體結構。這篇描述一下核心層的設計和實現。
至於核心層的設計,我想借鑑 一下微內核的思想。核心層只負責實現下面幾個功能:
1、 插件的加載,檢測,初始化。
2、 服務的註冊。
3、 服務的調用。
4、 服務的管理。
插件的加載,檢測,初始化
插件的加載利用linux共享庫的動態加載技術。具體的方法可以看一下IBM網站的一篇資料《Linux 動態庫剖析》 。
服務的註冊
服務的註冊與調用採用表驅動的方法。核心層中維護一個服務註冊表。
//插件間交互消息類型
typedef enum __Service_Type
{
Service_Max,
}Service_Type;
//插件用於和其他插件通信接口函數,由插件提供。
typedef PRsp_Ele_Stream (*PF_Invoke_Service_Func)(PReq_Ele_Stream pele_str);
//驅動表
typedef PF_Invoke_Service_Func Service_Drive_Table[Service_Max];
驅動表是一個數組,下標爲插件間交互消息類型,成員爲插件提供的接收的消息處理函數,由插件初始化的時候,調用插件框架的的註冊函數註冊到驅動表。
插件的初始化實現爲:
//插件用於註冊處理的消息類型的函數,由插件框架提供。
typedef RET_RESULT (*PF_Service_Register_Func)(Service_Type service_type);
//插件用於和其他插件通信接口函數,由插件框架提供。
typedef PRsp_Ele_Stream (*PF_Invoke_Service_Func)(PReq_Ele_Stream pele_str);
//插件回覆響應函數。插件收到異步請求後,處理完成後,發送響應消息給請求的插件。由插件框架提供
typedef void (*PF_Send_Response_Func)(PRsp_Ele_Stream pele_str);
//初始化插件信息
typedef struct Plugin_Init_St
{
PF_Service_Register_Func register_func;//服務註冊函數,要註冊一系列的枚舉值。插件可以處理的服務枚舉值
PF_Invoke_Service_Func invoke_serv_func;//和其他組件交互時,調用的用於和其他組件交互的函數。發送請求消息。
PF_Send_Response_Func send_rsp_func;//再設計一個回覆響應消息的接口。收到異步請求後,處理完畢後通知請求模塊處理結果。
} Plugin_Init_St, *PPlugin_Init_St;
//初始化插件函數,類似於構造函數。由插件提供,供插件框架加載插件時初始化插件使用。
void PF_Init_Plugin(PPlugin_Init_St pinit_info);
插件在函數PF_Init_Plugin中調用函數register_func來註冊插件要處理的消息類型。
服務的調用
//信元結構體
typedef struct Ele_St
{
Ele_Tag tag;
Ele_Length len;
Ele_Value value;
PEle_St next;
}Ele_St, *PEle_St;
//請求消息,信元流格式。
typedef struct Req_Ele_Stream
{
Plugin_ID src_id;//源插件id
Service_Type req_type;//請求類型
PEle_St ele;
} Req_Ele_Stream, *PReq_Ele_Stream;
//響應消息,信元流格式。
typedef struct Rsp_Ele_Stream
{
Plugin_ID dest_id;//目的插件id
Service_Type req_type;//響應對應的請求的類型。
Execute_Result result;//記錄執行結果
Execute_Reason reason;//記錄執行結果的原因
PEle_St ele;
} Rsp_Ele_Stream, *PRsp_Ele_Stream;
//接收插件調用服務請求函數,由插件提供,入參爲請求信元流。返回值爲響應信元流,用於同步請求處理。
PRsp_Ele_Stream PF_Receive_Invoke_Proc(PReq_Ele_Stream pele_str);
//插件收到響應消息的處理入口函數,由插件提供。如此爲響應信元流。
void PF_Receive_Rsponse_Porc(PRsp_Ele_Stream pele_str);
插件間的依賴關係是通過信元流來實現的。至於信元流的使用在我的另一篇博客《使用信元流(TLVStream)規範、簡化模塊(C/C++)間交互 》 中有描述。插件對外的接口都是統一的。
如果插件要和其他的插件通信,則調用PF_Init_Plugin函數的傳遞的服務調用接口: invoke_serv_func。插件框架根據信元流的類型,查找驅動表,找到對應的服務接收函數。插件用函數PF_Receive_Invoke_Proc接受其他插件的請求,此函數是插件想插件框架主動註冊到驅動表的。
如果服務時同步的,這直接通過此函數返回,返回的信息在響應信元流中。如果是異步的請求,這插件在處理完成後,通過 send_rsp_func函數來發送響應。
插件的卸載
//卸載插件時調用的函數,類似於析構函數。由插件提供,供插件框架卸載插件時調用。
void PF_Destroy_Func();