C# 外掛程式構架實戰

來源:互聯網
上載者:User
一、引言

    
1. 問題的引入

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

    
2. 幾個解決方案的對比

    我總結了一下我所接觸到的外掛程式構架,大致上可分為以下幾類: 

i> 指令碼式

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

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

ii> 動態函數庫 DLL

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

iii> 彙總式

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

iv> COM 組件

    COM [2] 的產生給這個世界增添了幾分活力。只有介面!我們的外掛程式需要做的只是實現程式定義的介面。主程式不需要知道外掛程式怎樣實現預定的功能,它只需要通過介面訪問外掛程式,並提供主程式相關對象的介面。這樣一來,主程式與各外掛程式之間的資訊交流就變得異常簡單。並且,外掛程式對於主程式來說是完全透明的。 
 
    
3. 決策

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

    下面,我們將以一個具有外掛程式構架的程式編輯器為例,來闡述這種構架在 C# 中的實現。 

二、設計過程

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

    
1. 介面設計

    我們的程式編輯器會向外掛程式開放正在編輯的文檔對象。程式啟動後,就枚舉每一個外掛程式並把它串連到主程式,同時傳遞主程式對象的介面。外掛程式可以通過這個介面來請求主程式對象或訪問主程式功能 。 

    根據上面的需求,我們首先需要一個主程式介面: 複製  儲存

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() 方法。這樣,外掛程式的生命才宣告結束。

   
2. 外掛程式資訊的儲存與擷取

    一個外掛程式需要有它的名稱 、版本等資訊。作為設計者的你,也一定要留下你的尊姓大名和個人網站等用來宣傳自己。 C# 的新特性——屬性, 就是一個很好的解決方案。因此我們定義一個從 System.Attribute 繼承來的類 PluginInfoArrtibute : 複製  儲存

////// 用來指定一個外掛程式的相關資訊///public class PluginInfoAttribute : System.Attribute{///    /// 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 = 0;// 暫時不會用    private bool _LoadWhenStart = true;}//用這個類修飾你的外掛程式,並讓他實現 IPlugin 介面:////// My Pluging 1( Just for test )///[PluginInfo("My Pluging 1( Just for test )","1.0","Jack H Hansen","http://blog.csdn.net/matrix2003b", true)]public class MyPlugin1 : IPlugin{public MyPlugin1() { }#region IPlugin 成員// 細節已略去#endregionprivate IApplicationObject _App;private IDocumentObject _CurDoc;}

3. 載入外掛程式

     現在就得用到 System.Refelction 命名空間了。程式在啟動時會搜尋 plugins 目錄下的每一個檔案。對於每一個檔案,如果它是一個外掛程式,就用 Assembly 對象載入它。然後枚舉程式集中的每一個對象。判斷一個程式集是否為我們的外掛程式的方法是判斷它是否直接或間接實現自 IPlugin。用下面的函數,傳遞從程式集枚舉的對象的System.Type。 複製  儲存

private bool IsValidPlugin(Type t){bool ret = false;Type[] interfaces = t.GetInterfaces();foreach (Type theInterface in interfaces){if (theInterface.FullName == "CSPluginKernel.IPlugin"){ret = true;break;}}return ret;}

若條件都滿足,IsValidPlugin() 就會返回 true 。接著程式就會建立這個對象並把它存於一個 ArrayList 中。 複製  儲存

plugins.Add(pluginAssembly.CreateInstance(plugingType.FullName));

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

三、原始碼

     由於篇幅所限,完整的原始碼(包含測試案例)請在下面的連結下載。
http://www.pscode.com/Upload_PSC/ftp/C__Plugin_1774987282004.zip
     下載後請用 VS.NET2003 開啟,重建解決方案即可(需要 .NET Framework 1.1)。測試案例是一個在 RichTextBox 控制項裡插入紅色文本的外掛程式。很簡單,只作測試之用。

四、結語

     That's all! 有了這種外掛程式構架,可憐的程式員們就再也不用為需求蔓延耗費心機了。另外,歡迎對本文以及本文的附加代碼作出評價。還有,就是,常去我的 Blog 看看~~ ^_^

註:

[1] 巴比倫塔的失敗   《人月神話》,Frederick P. Brooks Jr.  第 7 章 為什麼巴比倫塔會失敗 

[2] COM   有關 COM/COM+ 的詳細技術細節請參見《 Mastering COM and COM+ 》 , Ash Rofail , Yasser Shohoud.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.