本文討論一種簡單卻有效的插件體系結構,它使用C++,動態連接庫,基於面向對象編程的思想。首先來看一下使用插件機制能給咱們帶來哪些方面的好處,從而在適當時候合理的選擇使用。html
1. 加強代碼的透明度與一致性:java
由於插件一般會封裝第三方類庫或是其餘人編寫的代碼,須要清晰地定義出接口,用清晰一致的接口來面對全部事情。你的代碼也不會被轉換程序或是庫的特殊定製需求弄得亂七糟。linux
2. 改善工程的模塊化:c++
你的代碼被清析地分紅多個獨立的模塊,能夠把它們安置在子工程中的文件組中。這種解耦處理使得建立出的組件更加容易重用。程序員
3. 更短的編譯時間:編程
若是僅僅是爲了解釋某些類的聲明,而這些類內部使用了外部庫,編譯器再也不須要解析外部庫的頭文件了,由於具體實現是以私有的形式完成。windows
4. 更換與增長組件:設計模式
假如你須要向用戶發佈補丁,那麼更新單獨的插件而不是替代每個安裝了的文件更爲有效。當使用新的渲染器或是新的單元類型來擴展你的遊戲時,能過向引擎提供一組插件,能夠很容易的實現。數組
5. 在關閉源代碼的工程中使用GPL代碼:安全
通常,假如你使用了GPL發佈的代碼,那麼你也須要開放你的源代碼。然而,若是把GPL組件封裝在插件中,你就沒必要發佈插件的源碼。
介紹
先簡單解釋一下什麼是插件系統以及它如何工做:在普通的程序中,假如你須要代碼執行一項特殊的任務,你有兩種選擇:要麼你本身編寫,要麼你尋找一個已經存在 的知足你須要的庫。如今,你的要求變了,那你只好重寫代碼或是尋找另外一個不一樣的庫。不管是哪一種方式,都會致使你框架代碼中的那些依賴外部庫的代碼重寫。
如今,咱們能夠有另一種選擇:在插件系統中,工程中的任何組件再也不束縛於一種特定的實現(像渲染器既能夠基於OpenGL,也能夠選擇Direct3D),它們會從框架代碼中剝離出來,經過特定的方法被放入動態連接庫之中。
所謂的特定方法包括在框架代碼中建立接口,這些接口使得框架與動態庫解耦。插件提供接口的實現。咱們把插件與普通的動態連接庫區分開來是由於它們的加載方式 不一樣:程序不會直接連接插件,而多是在某些目錄下查找,若是發現便進行加載。全部插件均可以使用一種共同的方法與應用進行聯結。
常見的錯誤
一些程序員,當進行插件系統的設計時,可能會給每個做爲插件使用的動態庫添加一個以下函數相似的函數:
[cpp] view plaincopy
而後它們讓插件去提供一些類的實現。引擎用指望的對象名對加載的插件逐個進行查詢,直到某個插件返回,這是典型的設計模式中「職責鏈」模式的作法。
一些更聰明的程序員會作出新的設計,使插件在引擎中註冊本身,或是用定製的實現替代引擎內部缺省實現:
[html] view plaincopy
第一種設計的主要問題是:插件工廠建立的對象須要使用reinterpret_cast<>來進行轉換。一般,插件從共同基類(這裏指 PluginClass)派生,會引用一些不安全的感受。實際上,這樣作也是沒意義的,插件應該「默默」地響應輸入設備的請求,而後提交結果給輸出設備。
在這種結構下,爲了提供相同接口的多個不一樣實現,須要的工做變得異常複雜,若是插件能夠用不一樣名字註冊本身(如Direct3DRenderer and OpenGLRenderer),可是引擎不知道哪一個具體實現對用戶的選擇是有效的。假如把全部可能的實現列表硬編碼到程序中,那麼使用插件結構的目的也 沒有意義了。
假如插件系統經過一個框架或是庫(如遊戲引擎) 實現,架構師也確定會把功能暴露給應用程序使用。這樣,會帶來一些問題像如何在應用程序中使用插件,插件做者如何引擎的頭文件等,這包含了潛在的三者之間版本衝突的可能性。
單獨的工廠
接口,是被引擎清楚定義的,而不是插件。引擎經過定義接口來指導插件作什麼工做,插件具體實現功能。
咱們讓插件註冊本身的引擎接口的特殊實現。固然直接建立 插件實現類的實例並註冊是比較笨的作法。這樣使得同一時刻全部可能的實現同時存在,佔用內存與CPU資源。解決的辦法是工廠類,它惟一的目的是在請求時建立另外類的實例。
若是引擎定義了接口與插件通訊,那麼也應該爲工廠類定義接口:
[cpp] view plaincopy
選擇1: 插件管理器
接下來應該考慮插件如何在引擎中註冊它們的工廠,引擎又如何實際地使用這些註冊的插件。一種選擇是與存在的代碼很好的接合,這經過寫插件管理器來完成。這使得咱們能夠控制哪些組件容許被擴展。
[cpp] view plaincopy
當引擎須要一個渲染器時,它會訪問插件管理器,看哪些渲染器已經經過插件註冊了。而後要求插件管理器建立指望的渲染器,插件管理器因而使用工廠類來生成渲染器,插件管理器甚至不須要知道實現細節。
插件由動態庫組成,後者導出一個能夠被插件管理器調用的函數,用以註冊本身:
[cpp] view plaincopy
插件管理器簡單地在特定目錄下加載全部dll文件,檢查它們是否有一個名爲registerPlugin()的導出函數。固然也可用xml文檔來指定哪些插件要被加載。
選擇 2: 完整地集成Fully Integrated
除了使用插件管理器,也能夠從頭設計代碼框架以支持插件。最好的方法是把引擎分紅幾個子系統,構建一個系統核心來管理這些子系統。可能像下面這樣:
[cpp] view plaincopy
這 裏有兩個子系統,它們使用」Server」做爲後綴。第一個Server內部維護一個有效圖像加載器的列表,每次當用戶但願加載一幅圖片時,圖像加載器被一一查詢,直到發現一個特定 的實現能夠處理特定格式的圖片。
另外一個子系統有一個GraphicsDrivers的列表,它們做爲Renderers的工廠來使用。能夠是 Direct3DgraphicsDriver或是OpenGLGraphicsDrivers,它們分別負責Direct3Drenderer與 OpenGLRenderer的建立。引擎提供有效的驅動列表供用戶選擇使用,經過安裝一個新的插件,新的驅動也能夠被加入。
版本
在上面兩個可選擇的方法中,不強制要求你把特定的實現放到插件中。假如你的引擎提供一個讀檔器的默認實現,以支持自定義文件包格式。你能夠把它放到引擎自己,當StorageServer 啓動時自動進行註冊。
如今還有一個問題沒有討論:假如你不當心的話,與引擎不匹配(例如,已通過時的)插件會被加載。子系統類的一些變化或是插件管理器的改變足以致使內存佈局的 改變,當不匹配的插件試圖註冊時可能發生衝突甚至崩潰。比較討厭的是,這些在調試時難與發現。
幸運的是,辨認過期或不正確的插件很是容易。最可靠的是方法是在你的核心繫統中放置一個預處理常量。任何插件都有一個函數,它能夠返回這個常量給引擎:
[cpp] view plaincopy
在 這個常量被編譯到插件後,當引擎中的常量改變時,任何沒有進行從新編譯的插件它的 getExpectedEngineVersion ()方法會返回之前的那個值。引擎能夠根據這個值,拒絕加載不匹配的插件。爲了使插件能夠從新工做,必須從新編譯它。
固然,最大的危險是你忘記了更新常量值。不管如何,你應該有個自動版本管理工具幫助你。
英文原版:http://www.nuclex.org/articles/cxx/4-building-a-better-plugin-architecture
分類: 概念百科2012-07-07 19:18 801人閱讀 評論(0) 收藏 舉報
摘要:
基於插件的應用系統擁有良好的可擴充性、可定製性和可維護性。
<!--[if !supportLists]-->1. <!--[endif]-->引言
插件是近年來十分常見的一種技術。插件結構有助於編寫有良好的擴充和定製功能的應用程序。許多軟件甚至操做系統或其外殼程序都使用了這種技術,著名的使用插件機制的軟件是Winamp, Winamp早期的成功雖然在於其快速的解碼引擎,但在MP3播放器中可以保特長久的霸主地位。也正是因爲內置了健全的插件功能後期的Winamp中增長的MIDI、 MOD,、WAVE等音樂格式的播放功能徹底是靠插件實現的。本文將論述插件技術的基本原理,並給出三種不一樣的實現插件系統的方法。最重要的部分則是插件與主程序之間的交互插件,通常是一個遵循了某些特定規則的DLL,而主程序將全部插件接口在內存中的地址傳遞給插件插件則根據這些地址來調用插件接口完成所需功能獲取所需資源等。
<!--[if !supportLists]-->2. <!--[endif]-->插件系統的基本原理
插件的木質是在不修改程序主體的狀況下對軟件功能進行增強。當插件的接口被公開時任何人均可以本身製做插件來解決一些操做上的不便或增長一些功能。一個插件框架包括兩個部分:主程序(host)和插件((plug-in)。主程序便是「包含」插件的程序。插件必須實現若干標準接口,由主程序在與插件通訊時調用。
編程實現方面包括兩個部分:一部分是主體程序的插件處理機制,用來進行初始化每一個插件的過程,而且管理好每一個插件接口。另外一部分是插件的接口函數定義,將全部的插件接口函數進行封裝。以便開發者自由調用。
<!--[if !supportLists]-->3. <!--[endif]-->插件系統的開發
本文將經過一個摸擬的音頻播放器(使用VC++ 6。0)來介紹插件的三種實現方法
(1)普通的輸出函數的DLL方式
(2)使用C++的多態性。
(3)使用COM類別(category)機制。
首先對此音頻播放器做如下說明:①這不是一個真的播放器。固然也不能真地播放音頻文件。②每一個插件支持一種格式的音頻文件如Wma、mp3等,經過增長插件可使系統支持更多的音頻格式。③插件接口簡單,其功能實現只是彈出一個對話框代表哪一個插件的哪一個功能被調用了。製做這個播放器的真正目的是演示插件技術的原理和實現方法只要掌握了其原理和方法就徹底能夠開發出有用的插件系統
無論用什麼手段實現插件和主程序之間的交互必須有一個協議,對於方法(1)這個協議是一系列的函數。這些函數由插件DLL引出由主程序調用。對於方法〔2)協議則是一個(或多個)基類一般是抽象類,插件須要構造一個類來繼承此抽象類並實現其接口方法,再爲主程序提供一個建立和銷燬此實現類的對象的公共方法這個公共方法理所固然也應成爲協議的一部分。對於方法(3)則是一個(或多個)COM接口插件是一個COM組件,它實現了這些接口,並註冊到約定的組件類別tcomponent category)下。
通常音頻播放器都有這樣一些基本功能:裝載音頻文件(LoadFlle)、播放(Play)、暫停(Pause),中止((Stop)。咱們的播放器將提供這四個功能,但主程序自己並不會直接實現這些功能而是調用插件的實現,上文已經說過。每一個插件支持一種音頻格式,因此每一個插件的功能實現都是不一樣的。在主程序打開某個格式的音頻文件時,根據文件擴展名來決定調用哪一個插件的功能,主程序能夠在啓動時加載全部插件,也能夠在打開文件時動態加載所需插件,甚至能夠在啓動時加載一部分經常使用的插件,而在須要時加載其他插件開發者能夠有很高的自由度,如今咱們來詳細討論三種實現方法,
3.1第一種方法
3.1.1插件的實現
咱們建立一個動態連接庫PIugl.dll,爲了支持四個基本功能,它輸出相應的四個函數
void LoadFile(const char szHeName)
void Play(),
void Pause(),
void Stop(),
這些函數能夠簡單實現爲只彈出一個消息框,代表是哪一個插件的哪一個函數被調用了, 爲了使主程序在運行時能知道這個插件能夠支持什麼格式的音頻又件插件程序還應輸出一個函數供主程序查詢用
void GetSupportedformat(char* szFomrat)
至此,這個插件就製做完了,能夠依樣畫葫蘆再作一個PIug2,dll它‘支持‘,wma文件,下面來看主程序的實現。
3.1.2主程序的實現
主程序是一個基於對話框的標準Windows程序,它啓動時會搜索約定目錄(能夠約定全部插件都存放在主程序所在目錄的Plugins子目錄下),並使用Wia32函數LoadLrbrary加載全部插件,每加載一個插件DLL,就調用另外一個Wun32函數GetProcAddress獲取引出函數
GetSupportedformat的地址,並調用此函數返回插件所支持的格式名(便是音頻文件的擴展名),而後把(格式名,DLL句柄)二元組保存下來,當用戶經過菜單打開文件時,主程序會根據擴展名決定調用哪一個插件的LoadFile函數,並指明此插件DLL的句柄爲當前使用的插件的DLL句柄(好比保存到變量m_hlnst中),此後當用戶
經過按鈕調用Play等其餘功能時就調用句柄爲m_hlnst的插件的相應功能,如:
typedef void (PLAY)();
if(m_hlnst)
{
PLAY=GetProcAddress(m_hlnst, "Play");
PLAY();
}
另外,當程序退出時,應該調用FreeLibrary函數卸載插件,
到此爲止第一種實現插件系統的方法就介紹完了,能夠看出,其實現的關鍵在於插件輸出、函數的約定以及把插件所支持的格式名映射到插件DLL的句柄,後面將會看到,實際上每一種實現都是基於這種原理只不過是方式不一樣而已。
3.2第二種方法
第一種實現方法徹底是結構化程序設計,存在接口不易維護等缺點,從而咱們天然而然想到面向對象的解決方案——把API封裝到類裏。
3.2.1插件的實現
咱們定義抽象類以下
class ICppPlugin
{
public:
ICppPlugin ();
virtual void ICppPluginIcon()=0;
virtual void Release()=0;
virtual void GetSupportedFormat(char* szFormat)=0;
virtual void Load(constchar* szHeName)=0;
virtual void Play()=0;
virtual void Stop()=0;
virtual void Pause()=0;
};
其中Release成員函數將在後面介紹,其餘成員函數意義與第一種實現中的同名函數相同,插件程序須要實現此抽象類,每一個插件都有不一樣的實現而主程序僅經過接口(抽象類)來訪問它們,如今來製做一個插件CppPlugin I,dll,它包含繼承於ICppPlugin的類CppPlugin1。
class CppPlugin1 : public ICppPlugin //實現代碼略
爲使主程序能建立CppPlugin1對象,插件輸出一個函數
bool CreateObject(void* pObj) {
pObj=new CppPluginlo;
return *pObj!=NULL;
}
對象是在動態庫中建立的,也應該在動態庫中被銷燬,這就是ICppplugin的成員函數Release的做用。當主程序使用完對象後,須要調用此函數來銷燬對象。它的實現以下:
void CppPIugin1::Release() { delete this; //刪除本身}
咱們還能夠再製做更多的插件,這些插件只須要給出ICppPlugin的不一樣實現,即改變類
Cppplugin1的成員函數實現便可,如今來看主程序的處理過程。
3.2.2主程序的實現
插件的加載過程與第一種方法類似,所不一樣的是加載DLL後,首先調用的是插件程序的CreateObject輸出函數來建立對象:
typedef boot (*CreatoObject)(void *pObj);
//定義一個函數指針類型,獲取Create06ject的地址,hInst爲DLL的句柄
Create0bject CreatcObj=(CreateObject)GetProcAddtess(hInst, "Create0bject");
ICppPlugin* pObj = 0; //定義一個ICPPPlugjn的指針
CreateObj((void**)&pObj);//建立對象
接下來查詢插件所支持的格式名,本方式中GetSupportedFormat已成爲ICppPlugin的成員函數。
CString str;
pObj->GetSupportedFotmat(str,GetBuffer(8));
str.ReleaseBuffer;
另外,須要保存的除(格式名,DLL句柄)二元組映射外,還需保存(格式名,建立對象函數指針)二元組映射以備後用。
fstr存放的是格式名字符串的小寫形式
m_formatMap [fstr」= hInst;
m_factoryMap[fstr] = CreatcObj;
一樣在打開文件時選擇使用哪一個插件,m_pObj存放當前使用的對象的指針,定義以下:ICppplugin *m_p0bj=0;//在程序初始化時要把它置爲0
if(m_pObj){
m_pObj->Release();
m_pObj=0;
}
m_factoryMap [strExj((void")&m_pObj); //用CreatcObject
m_pObj->LoadFile((LPCSTR)strFileName); //strFileName是音頻文件全路徑名
之後就可使用m_pObj來調用其餘操做了,例如:
if(m_pObj) m_pObj->Play();
在主程序退出時須要卸載DLL,沒必要重複。
如今第二種實現插件系統的方式也介紹完了,這種方式基於C++的多態性,須要注意的是對象的建立和銷燬方式,
3.3第三種方法
第二種實現方法其實已是組件化程序的雛形了,能夠勝任開發小型的插件系統,若要開發大中型的系統,則須要徹底組件化的設計,COM (Component Object Model,組件對象模型)實際上就是一個實現插件的極好技術,基於COM創建的插件系統,主程序和各個插件能夠用不一樣的編程語言寫成((C++, VB, Delplu,Java等),COM能使它們無縫地結合在一塊兒,篇幅所限本文不詳細介紹COM的原理與編程。
在這種實現方法中,插件是一個COM組件,確切地說,插件程序做爲COM組件程序,包含了一個或多個COM對象。這些COM對象都實現了相同的COM接口,主程序經過這個COM接口來訪問COM對象,即COM接口是主程序與插件通訊的惟一手段幾好比播放器插件所包含的COM對象都實現了以下COM接口(IDL定義):
interface ICppPlugin : fUnknown
Plugin、子目錄下,可是由於COM組件對COM客戶是位置透明的,因此主程序須要知道的已不是插件的具休位置和名字而是COM組件的CLSID或ProglD,能夠選擇把這些信息存放到指定的註冊表子鍵下,也能夠放到ini文件中等等,然而更好的方式是使用COM的組件類別
(Component Category)索引機制,
COM容許實現者能夠把相關的一組COM類組織到邏輯組(即組件類別)中,一般一個類別(category)中的全部COM類都實現同一組接口這些COM類共享同一個類別ID,稱爲CATID (category ID), CATID也是GUID它做爲COM類的屬牲被保存在註冊表中COM類的"Implemented Categories’子鍵下在組件自注冊時加入,每一個類別在注朋表中都有它本身惟一的子鍵,由它的CATID命名,
另外,系統提供一個稱爲組件類別管理器(component category manager)的COM類,它實現了ICatRegrster和ICatlafomauon接口,分別用來註冊和查詢類別信息,
因而基於COM的插件系統就能夠這樣實現:
(1)註冊一個組件類別CATID_Plugin,
(2)插件實現包含實現了ICppPlugin接口的COM類並註冊爲CATID_Plugin類別,
(3〕主程序在啓動時使用組件類別管理器查詢CATID一Plugin類別信息,獲得此類別的全部COM類的CLSID,並建立相應的COM對象,獲取其ICppPlugin接口,而後調用接口的GetSupportedFormat,方法獲得該插件所支持的格式名,保存(格式名,ICppPlugin接口指針)映射。
(4)程序在打開音頻文件時根據擴展名決定使用哪一個ICppPlugin接口指針調用LoadFile方法,並設置當前使用的接口指針m_pICppPlugin爲該接口指針。
(5)之後的操做(Play等)都使用m_pICppPlugin來調用直到打開不一樣類型的文件。
(6)程序退出時釋放掉COM對象並釋放COM庫所佔用的資源。
詳細代碼這裏再也不給出。
至此三種建立插件系統的方式都介紹完畢,程序使用Visual C++6,0開發,在Windows 2000Server上運行經過,
3.4 小結
上文所演示的例子中調用是單向的,即由插件暴露出接口,由主程序來調用,在實際應用中主程序也徹底能夠暴露出接口,由插件來調用從而使系統更加靈活,三種方法從結構化程序設計到面向對象的方法再到基於組件的軟件開發,難度依次升高,功能逐漸強大,系統也愈來愈靈活,根據所要建立的插件系統的不一樣開發人員能夠選擇合適的實現方式,掌握技術原理是容易的,其實真正困難的是如何進行詳細的應用分析,抽象出合適的接口,這樣才能使整個插件系統擁有強大的可擴展性靈活性、健壯性和良好的可維護性。
HRESULT LoadFile(BSTR bstrFileName);
HRESULT GetSupportedFormat([out,retval) BSTR pbstrFormat);
HRESULT Play();
HRESULT Stop();
HRESULT Pause(); };
因而,插件的開發就是COM組件的開發,這裏再也不詳述,惟一的問題是主程序如何知道哪些是它能使用的插件(就是COM組件)。前兩種實現中,咱們須要插件的具體位置和名字,因此約定插件都存放在主程序所在目錄的。
<!--[if !supportLists]-->4. <!--[endif]-->結語
插件做爲特殊的組件,具有組件的全部優秀的特性,這些特性使其在開發,推廣,應用方面有重要的現實意義,基於插件技術的軟件開發可使產品專業化標準化系列化,經過不一樣規格和系列的插件的組合,能夠快速地完成應用系統原型而經過對插件的局部修改來知足客戶的需求和升級。
<!--[if !supportLists]-->5. <!--[endif]-->參考書目
Windows高級編程指南(第三版),清華大學出版社1999,6,
設計模式-可複用面向對象軟件的基礎,機械工業出版社2000,9,
COM本質論,中國電力出版社2001s,
分類: C/C++2010-04-10 23:39 1986人閱讀 評論(0) 收藏 舉報
框架eclipse插件servicestreamosgi測試
——初步設想
最近一直在學習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)支持分佈式,模塊能夠調用框架外的插件。
採用的技術
插件框架要解決的一個問題就是插件的動態加載能力。這裏可使用共享庫的動態加載技術。固然,爲了簡單,第一步只考慮作一個linux下的插件框架。
整體結構
框架的整體結構上,參考OSGI的「微內核+系統插件+應用插件」結構。這裏要好好考慮一下把什麼作在內核中。關於微內核結構,之前我作個一個微內核流程引擎,會在後面有時間和你們分享。
框架中模塊間的數據傳送,有兩種解決方法:一是普元採用的XML數據總線的作法。優勢是擴展性好,可讀性好。可是速度有些慢。二是採用我熟悉的信元流。優勢的效率高,訪問方便,可是可讀性差一點,另外跨框架的數據傳送,須要考慮網絡字節序的問題。
對於框架間的通訊,經過系統插件封裝,對應用插件隱藏通訊細節。
部署
努力作到一鍵式部署。
——整體功能
在這一系列的上一個文章中,介紹了構建C/C++插件開發框架的初步設想,下面我會一步步的向下展開,來實現個人這個設想。
今天主要談一下我對這個框架的功能認識,或是指望。昨天看了一篇關於持續集成能力成熟度模型 的一篇文章,受此啓發,我對此框架的認識漸漸清晰。
這個框架能夠當作咱們公司底層產品(交換機,資源服務器等)的基礎設施。上層基於java開發的產品能夠直接在OSGI上開發。
核心功能:
一、最重要的一個功能是,提供一個模塊化的編程模型,促進模塊化軟件開發,真正的實現針對接口編程。
二、提供一個有助於提升模塊可重用性的基礎設施。
三、提供一個C/C++插件的運行環境。
四、提供一個動態插件框架,插件能夠動態更改,而無需重啓系統。這個功能雖然不難實現,可是用處好像不是很大。
--------------------------------------------------------------------------------
擴展部分功能:
一、支持分佈式系統結構,多個運行框架組合起來造成一個系統,對模塊內部隱藏遠程通信細節。
二、支持系統的分層架構。
三、可以和其餘的開發框架進行集成,好比OSGI,SCA等。
四、多個運行框架中,可以實現對運行框架的有效管理。
五、概念上要實現相似於SCA中component(構件),composite(組合構件),Domain(域)的概念。
--------------------------------------------------------------------------------
開發部分功能:
一、爲了簡化開發,開發一個Eclipse插件,用於開發框架中的C/C++插件。可以根據插件開發嚮導,最終生成符合插件規範的公共代碼,配置文件,Makefile文件等。
--------------------------------------------------------------------------------
調試部分功能:
一、提供一個統一的日誌處理函數,能夠集成Log4cpp。
二、提供模塊間的消息日誌,以及框架對外的接口日誌。
三、提供消息和日誌的追蹤功能,能將和某事件相關的消息和日誌單獨提取出來。
四、提供資源監測功能,監測對資源(內存,套接字,文件句柄等)的使用狀況。
--------------------------------------------------------------------------------
測試部分功能:
一、集成一些單元測試框架,好比unitcpp,達到自動化單元測試的目標。
二、本身實現自動化集成測試框架,而且開發相應的Eclipse插件,簡化集成測試(利用腳本和信元流)。
三、集成原有的自動化功能測試框架flowtest,而且開發相應的Eclipse插件,簡化功能測試。
四、實現性能測試,監測框架。
--------------------------------------------------------------------------------
部署部分功能:
一、實現自動化部署。特別是在分佈式應用的狀況下。
二、提供一個命令行程序,經過命令更改系統配置,管理插件。
——整體結構
這幾天爲了設計插件開發框架,嘗試用了一下發散思惟來思考問題。中間看過依賴注入,AOP(面向方面編程),以及契約式設計等。雖然有些工具沒法直接使用,可是這些思想仍是能夠借鑑的,好比依賴注入,契約式設計。至於AOP,和工具相關性較大,雖然思想不錯,可是沒法直接在C++中使用。
我設計的插件間的依賴不是經過接口實現的,而是經過插件間的數據(信元流)。而信元流的檢測可使用契約來檢查。
插件開發框架的整體結構
微內核 :
一、 負責插件的加載,檢測,初始化。
二、 負責服務的註冊。
三、 負責服務的調用。
四、 服務的管理。
擴展層:
一、 日誌的打印。
二、 消息(信元流)的解釋,將二進制格式解釋爲文本。便於定位。
三、 消息和日誌的追蹤。
分佈式處理層:
一、 用於和其餘的框架通訊。
二、 和其餘的框架搭配,造成一個分佈式的系統。
自動化測試框架層:
一、 集成 cppunit 。
二、 自動化集成測試框架。
三、 自動化功能測試框架。
和第三方框架集成層:
1 、和 第三方框架 集成層。
——核心層設計和實現
上面一篇文章大體描述了一下插件開發框架總體結構。這篇描述一下核心層的設計和實現。
至於核心層的設計,我想借鑑 一下微內核的思想。核心層只負責實現下面幾個功能:
一、 插件的加載,檢測,初始化。
二、 服務的註冊。
三、 服務的調用。
四、 服務的管理。
插件的加載,檢測,初始化
插件的加載利用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();