熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> ASP編程 >> 正文

Visual C# 插件構架實戰

2013-11-15 12:54:27  來源: ASP編程 

  引言

  

   問題的引入

   

  假設你設計的程序已經部署到用戶的計算機上並且能夠正常運行了但是有一天用戶打來了電話——他們要求增加新的功能確定了用戶的需求後你竟然發現原有的軟件架構已經無法勝任新增任務的需求——你需要重新設計這個應用了!但問題是就算你又用了一個開發周期完成了用戶需要的應用卻不能保證用戶的需求不會再次變更也就是說需求蔓延的可能性依然存在因此這種情況下插件構架更能顯示出它的優越性

   

   幾個解決方案的對比

   

  我總結了一下我所接觸到的插件構架大致上可分為以下幾類

   

  i> 腳本式

   

  使用某種語言把插件的程序邏輯寫成腳本代碼而這種語言可以是 Python 或是其他現存的已經經過用戶長時間考驗的腳本語言甚至你可以自行設計一種腳本語言來配合你程序的特殊需要當然用當今最流行的 XML 是再合適不過了

   

  這種形式的特點在於稍有點編程知識的用戶就可以自行修改你的腳本( ^_^ 假如你不加密它的話)我們無法論證這是好處還是壞處因為這種情況所造成的後果是不可預知的

   

  ii> 動態函數庫 DLL

   

  插件功能以動態庫函數的形式存在主程序通過某種渠道(插件編寫者或某些工具)獲得插件 DLL 中的函數簽名然後在合適的地方調用它們用過 Matlab 的讀者都知道 Matlab 中的各項功能幾乎都是些動態鏈入的函數

   

  iii> 聚合式

   

  顧名思義就是把插件功能直接寫成 EXE 主程序除了完成自己的職責外還負責調度這些插件我不喜歡這種形式這使插件與插件之間主程序與插件之間(主要是這一點)的信息交流困難了許多巴比倫塔的失敗 [] 從某種程度上講就是信息交流無法實現造成的

   

  iv> COM 組件

   

  COM [] 的產生給這個世界增添了幾分活力只有接口!我們的插件需要做的只是實現程序定義的接口主程序不需要知道插件怎樣實現預定的功能它只需要通過接口訪問插件並提供主程序相關對象的接口這樣一來主程序與各插件之間的信息交流就變得異常簡單並且插件對於主程序來說是完全透明的

   

   決策

   

  C# 是面向對象的程序設計語言它提供了 interface 關鍵字來直接定義接口同時 SystemReflection 命名空間也提供了訪問外部程序集的一系列相關對象這就為我們在 C# 中實現插件構架打下了堅實的基礎

   

  下面我們將以一個具有插件構架的程序編輯器為例來闡述這種構架在 C# 中的實現

  

  設計過程

   

  好了現在我們准備把所有的核心代碼都放在 CSPluginKernel 命名空間中用VSIDE建立一個C#類庫工程在命名空間 CSPluginKernel 中開始我們的代碼

   

   接口設計

   

  我們的程序編輯器會向插件開放正在編輯的文檔對象程序啟動後就枚舉每一個插件並把它連接到主程序同時傳遞主程序對象的接口插件可以通過這個接口來請求主程序對象或訪問主程序功能

   

  根據上面的需求我們首先需要一個主程序接口

   

  public interface IApplicationObject {

  void Alert( string msg ); // 產生一條信息

  void ShowInStatusBar( string msg ); // 將指定的信息顯示在狀態欄

  IDocumentObject QueryCurrentDocument(); // 獲取當前使用的文檔對象

  IDocumentObject[] QueryDocuments(); // 獲取所有的文檔對象

  // 設置事件處理器

  void SetDelegate( Delegates whichOne EventHandler targer );

  }

   

  // 目前只需要這一個事件

   

  public enum Delegates {

  Delegate_ActiveDocumentChanged

  }

   

  然後是 IDocumentObject 接口插件通過這個接口訪問編輯器對象

   

  ///

  /// 編輯器對象必須實現這個接口

  ///

   

  public interface IDocumentObject {

  // 這些屬性是 RichTextBox 控件的相應的屬性映射

  string SelectionText { get ; set ; }

  Color SelectionColor { get ; set ; }

   

  Font SelectionFont { get ; set ; }

  int SelectionStart { get ; set ; }

  int SelectionLength { get ; set ; }

  string SelectionRTF { get ; set ; }

  bool HasChanges { get ; }

  void Select( int start int length );

  void AppendText( string str );

  void SaveFile( string fileName );

  void SaveFile();

  void OpenFile( string fileName );

  void CloseFile();

  }

   

  這個接口不需要過多解釋這裡我只實現了RichTextBox控件少數的幾個方法其他可能用得到的讀者自行添加即可

   

  再然後根據插件在其生命周期裡的行為設計插件的接口

   

  ///

  /// 本程序的插件必須實現這個接口

  ///

   

  public interface IPlugin {

  ConnectionResult Connect( IApplicationObject app );

  void OnDestory();

  void OnLoad();

  void Run();

  }

   

  ///

  /// 表示插件與主程序連接的結果

  ///

  public enum ConnectionResult {

  Connection_Success

  Connection_Failed

  }

   

  主程序會首先調用 Connect() 方法並傳遞 IApplicationObject 給插件插件在這個過程中做一些初始化工作然後插件的 OnLoad() 方法被調用在這之後當主程序接收到調用插件的信號時(鍵盤鼠標響應)就會調用插件的 Run() 方法來啟動這個插件程序結束時調用其 OnDestory() 方法這樣插件的生命才宣告結束

   

   插件信息的存儲與獲取

   

  一個插件需要有它的名稱 版本等信息作為設計者的你也一定要留下你的尊姓大名和個人網站等用來宣傳自己 C# 的新特性——屬性 就是一個很好的解決方案因此我們定義一個從 SystemAttribute 繼承來的類 PluginInfoArrtibute

   

  ///

  /// 用來指定一個插件的相關信息

  ///

   

  public class PluginInfoAttribute : SystemAttribute

  {

  ///

  /// Deprecated Do not use

  ///

  public PluginInfoAttribute() {}

  public PluginInfoAttribute(

  string name string version

  string author string webpage bool loadWhenStart ) {

  // 細節已略去

  }

   

  public string Name { get { return _Name; } }

  public string Version { get { return _Version; } }

  public string Author { get { return _Author; } }

  public string Webpage { get { return _Webpage; } }

  public bool LoadWhenStart { get { return _LoadWhenStart; } }

  ///

  /// 用來存儲一些有用的信息

  ///

  public object Tag {

  get { return _Tag; }

  set { _Tag = value ; }

  }

  ///

  /// 用來存儲序號

  ///

  public int Index {

  get { return _Index; }

  set { _Index = value ; }

  }

   

  private string _Name = ;

  private string _Version = ;

  private string _Author = ;

  private string _Webpage = ;

  private object _Tag = null ;

  private int _Index = ;

  // 暫時不會用

  private bool _LoadWhenStart = true ;

  }

   

  用這個類修飾你的插件並讓他實現 IPlugin 接口

   

  ///

  /// My Pluging ( Just for test )

  ///

  [

  PluginInfo(My Pluging ( Just for test ) Jack H Hansen

   true )

  ]

   

  public class MyPlugin : IPlugin {

  public MyPlugin() { }

  #region IPlugin 成員

  // 細節已略去

  #endregion

  private IApplicationObject _App;

  private IDocumentObject _CurDoc;

  }

   

   加載插件

   

  現在就得用到 SystemRefelction 命名空間了程序在啟動時會搜索 plugins 目錄下的每一個文件對於每一個文件如果它是一個插件就用 Assembly 對象加載它然後枚舉程序集中的每一個對象判斷一個程序集是否為我們的插件的方法是判斷它是否直接或間接實現自 IPlugin用下面的函數傳遞從程序集枚舉的對象的SystemType

   

  private bool IsValidPlugin( Type t ) {

  bool ret = false ;

  Type[] interfaces = tGetInterfaces();

  foreach ( Type theInterface in interfaces ) {

  if ( theInterfaceFullName == CSPluginKernelIPlugin ) {

  ret = true ;

  break ;

  }

  }

  return ret;

  }

   

  若條件都滿足IsValidPlugin() 就會返回 true 接著程序就會創建這個對象並把它存於一個 ArrayList 中

   

  pluginsAdd( pluginAssemblyCreateInstance( plugingTypeFullName ) );

   

  現在你就可以撰寫測試代碼了


From:http://tw.wingwit.com/Article/program/ASP/201311/21857.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.