用C++實現的一種外掛程式體繫結構—–概述

來源:互聯網
上載者:User

本文討論一種簡單卻有效外掛程式體繫結構,它使用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

 有範例程式碼下載。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.