1. 程式人生 > >用C++實現外掛體系結構

用C++實現外掛體系結構

 

本文討論一種簡單卻有效的外掛體系結構,它使用C++,動態連結庫,基於面向物件程式設計的思想。首先來看一下使用外掛機制能給我們帶來哪些方面的好處,從而在適當時候合理的選擇使用。

1. 增強程式碼的透明度與一致性:

因為外掛通常會封裝第三方類庫或是其他人編寫的程式碼,需要清晰地定義出介面,用清晰一致的介面來面對所有事情。你的程式碼也不會被轉換程式或是庫的特殊定製需求弄得亂七糟。

2. 改善工程的模組化:

你的程式碼被清析地分成多個獨立的模組,可以把它們安置在子工程中的檔案組中。這種解耦處理使得創建出的元件更加容易重用。

3. 更短的編譯時間:

如果僅僅是為了解釋某些類的宣告,而這些類內部使用了外部庫,編譯器不再需要解析外部庫的標頭檔案了,因為具體實現是以私有的形式完成。

4. 更換與增加元件:

假如你需要向用戶釋出補丁,那麼更新單獨的外掛而不是替代每一個安裝了的檔案更為有效。當使用新的渲染器或是新的單元型別來擴充套件你的遊戲時,能過向引擎提供一組外掛,可以很容易的實現。

5. 在關閉原始碼的工程中使用GPL程式碼:

一般,假如你使用了GPL釋出的程式碼,那麼你也需要開放你的原始碼。然而,如果把GPL元件封裝在外掛中,你就不必釋出外掛的原始碼。

介紹

 先簡單解釋一下什麼是外掛系統以及它如何工作:在普通的程式中,假如你需要程式碼執行一項特殊的任務,你有兩種選擇:要麼你自己編寫,要麼你尋找一個已經存在 的滿足你需要的庫。現在,你的要求變了,那你只好重寫程式碼或是尋找另一個不同的庫。無論是哪種方式,都會導致你框架程式碼中的那些依賴外部庫的程式碼重寫。

現在,我們可以有另外一種選擇:在外掛系統中,工程中的任何元件不再束縛於一種特定的實現(像渲染器既可以基於OpenGL,也可以選擇Direct3D),它們會從框架程式碼中剝離出來,通過特定的方法被放入動態連結庫之中。

所謂的特定方法包括在框架程式碼中建立介面,這些介面使得框架與動態庫解耦。外掛提供介面的實現。我們把外掛與普通的動態連結庫區分開來是因為它們的載入方式 不同:程式不會直接連結外掛,而可能是在某些目錄下查詢,如果發現便進行載入。所有外掛都可以使用一種共同的方法與應用進行聯結。

常見的錯誤

一些程式設計師,當進行外掛系統的設計時,可能會給每一個作為外掛使用的動態庫新增一個如下函式類似的函式:

PluginClass *createInstance(const char*); 

然後它們讓外掛去提供一些類的實現。引擎用期望的物件名對載入的外掛逐個進行查詢,直到某個外掛返回,這是典型的設計模式中“職責鏈”模式的做法。

一些更聰明的程式設計師會做出新的設計,使外掛在引擎中註冊自己,或是用定製的實現替代引擎內部預設實現:

Void dllStartPlugin(PluginManager &pm); 
Void dllStopPlugin(PluginManager &pm); 

第一種設計的主要問題是:外掛工廠建立的物件需要使用reinterpret_cast<>來進行轉換。通常,外掛從共同基類(這裡指 PluginClass)派生,會引用一些不安全的感覺。實際上,這樣做也是沒意義的,外掛應該“默默”地響應輸入裝置的請求,然後提交結果給輸出裝置。

在這種結構下,為了提供相同介面的多個不同實現,需要的工作變得異常複雜,如果外掛可以用不同名字註冊自己(如Direct3DRenderer and OpenGLRenderer),但是引擎不知道哪個具體實現對使用者的選擇是有效的。假如把所有可能的實現列表硬編碼到程式中,那麼使用外掛結構的目的也 沒有意義了。

假如外掛系統通過一個框架或是庫(如遊戲引擎) 實現,架構師也肯定會把功能暴露給應用程式使用。這樣,會帶來一些問題像如何在應用程式中使用外掛,外掛作者如何引擎的標頭檔案等,這包含了潛在的三者之間版本衝突的可能性。

單獨的工廠

介面,是被引擎清楚定義的,而不是外掛。引擎通過定義介面來指導外掛做什麼工作,外掛具體實現功能。

我們讓外掛註冊自己的引擎介面的特殊實現。當然直接建立 外掛實現類的例項並註冊是比較笨的做法。這樣使得同一時刻所有可能的實現同時存在,佔用記憶體與CPU資源。解決的辦法是工廠類,它唯一的目的是在請求時建立另外類的例項。

如果引擎定義了介面與外掛通訊,那麼也應該為工廠類定義介面:

template<typename Interface> 
class Factory { 
   virtual Interface *create() = 0;
 }; 

class Renderer { 
   virtual void beginScene() = 0; 
   virtual void endScene() = 0; 
};

 typedef Factory<Renderer> RendererFactory; 

選擇1: 外掛管理器

接下來應該考慮外掛如何在引擎中註冊它們的工廠,引擎又如何實際地使用這些註冊的外掛。一種選擇是與存在的程式碼很好的接合,這通過寫外掛管理器來完成。這使得我們可以控制哪些元件允許被擴充套件。

 class PluginManager { 
    void registerRenderer(std::auto_ptr<RendererFactory> RF); 
    void registerSceneManager(std::auto_ptr<SceneManagerFactory> SMF);
 };

當引擎需要一個渲染器時,它會訪問外掛管理器,看哪些渲染器已經通過外掛註冊了。然後要求外掛管理器建立期望的渲染器,外掛管理器於是使用工廠類來生成渲染器,外掛管理器甚至不需要知道實現細節。

外掛由動態庫組成,後者匯出一個可以被外掛管理器呼叫的函式,用以註冊自己:

void registerPlugin(PluginManager &PM);

外掛管理器簡單地在特定目錄下載入所有dll檔案,檢查它們是否有一個名為registerPlugin()的匯出函式。當然也可用xml文件來指定哪些外掛要被載入。

選擇 2: 完整地整合Fully Integrated

除了使用外掛管理器,也可以從頭設計程式碼框架以支援外掛。最好的方法是把引擎分成幾個子系統,構建一個系統核心來管理這些子系統。可能像下面這樣:

class Kernel {
   StorageServer &getStorageServer() const; 
   GraphicsServer &getGraphicsServer() const;
 }; 

class StorageServer {
     //提供給外掛使用,註冊新的讀檔器 
    void addArchiveReader(std::auto_ptr<ArchiveReader> AL); 
     // 查詢所有註冊的讀檔器,直到找到可以開啟指定格式的讀檔器 
    std::auto_ptr<Archive> openArchive(const std::string &sFilename); 
}; 

class GraphicsServer { 
      // 供外掛使用,用來新增驅動 
    void addGraphicsDriver(std::auto_ptr<GraphicsDriver> AF); 
      // 獲取有效圖形驅動的數目 
    size_t getDriverCount() const; 
      //返回驅動 
    GraphicsDriver &getDriver(size_t Index);
 }; 

這 裡有兩個子系統,它們使用”Server”作為字尾。第一個Server內部維護一個有效影象載入器的列表,每次當用戶希望載入一幅圖片時,影象載入器被一一查詢,直到發現一個特定 的實現可以處理特定格式的圖片。

另一個子系統有一個GraphicsDrivers的列表,它們作為Renderers的工廠來使用。可以是 Direct3DgraphicsDriver或是OpenGLGraphicsDrivers,它們分別負責Direct3Drenderer與 OpenGLRenderer的建立。引擎提供有效的驅動列表供使用者選擇使用,通過安裝一個新的外掛,新的驅動也可以被加入。
版本

在上面兩個可選擇的方法中,不強制要求你把特定的實現放到外掛中。假如你的引擎提供一個讀檔器的預設實現,以支援自定義檔案包格式。你可以把它放到引擎本身,當StorageServer 啟動時自動進行註冊。

現在還有一個問題沒有討論:假如你不小心的話,與引擎不匹配(例如,已經過時的)外掛會被載入。子系統類的一些變化或是外掛管理器的改變足以導致記憶體佈局的 改變,當不匹配的外掛試圖註冊時可能發生衝突甚至崩潰。比較討厭的是,這些在除錯時難與發現。

幸運的是,辨認過時或不正確的外掛非常容易。最可靠的是方法是在你的核心繫統中放置一個預處理常量。任何外掛都有一個函式,它可以返回這個常量給引擎:

 // Somewhere in your core system 
#define MyEngineVersion 1; 
// The plugin 
extern int getExpectedEngineVersion() {
  return MyEngineVersion; 
}

在 這個常量被編譯到外掛後,當引擎中的常量改變時,任何沒有進行重新編譯的外掛它的 getExpectedEngineVersion ()方法會返回以前的那個值。引擎可以根據這個值,拒絕載入不匹配的外掛。為了使外掛可以重新工作,必須重新編譯它。

當然,最大的危險是你忘記了更新常量值。無論如何,你應該有個自動版本管理工具幫助你。

分類: 概念百科 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++ 1986人閱讀 評論(0) 收藏 舉報

——初步設想

 最近一直在學習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++外掛開發框架的要素

  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、  服務的呼叫。