轉:用C++實現的一種插件體系結構-----概述

用C++實現的一種插件體系結構-----概述html

本文討論一種簡單卻有效的插件體系結構,它使用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 ()方法會返回之前的那個值。引擎能夠根據這個值,拒絕加載不匹配的插件。爲了使插件能夠從新工做,必須從新編譯它。固然,最大的危險是你忘記了更新常量值。不管如何,你應該有個自動版本管理工具幫助你。編程

 英文原文地址:http://www.nuclex.org/articles/building-a-better-plugin-architecture
 有示例代碼下載。設計模式

相關文章
相關標籤/搜索