C# 插件式程序開發
在網上找了下插件式編程的資料,這里自己先借鑒下別人的,同時發現有自己的看法,不過由于本人水平有限,不一定有參考價值,寫出來一方面是為了總結自己,以求提高,另一方面也希望各為朋友看到我的不足,給我提出寶貴意見。
什么是插件式編程
提起插件式,我們首先想到的是firefox, 用過firefox的人都知道它是一個插件式程序。當一個功能需要,完全可以從網上下載一個插件后,重啟后,就能使用。這個功能給我們帶來許多的方便之處,這就是插件式程序的好處。
插件的本質在于不修改程序主體(平臺)的情況下對軟件功能進行拓展與加強,當插件的接口公開后,任何公司或個人都可以制作自己的插件來解決一些操作上的不便或增加新功能,也就是真正意義上實現“即插即用”軟件開發。
平臺+插件軟件結構是將一個待開發的目標軟件分為兩部分,一部分為軟件的主體或框架,可定義為平臺,這是預先編譯后的程序。另一部分為功能或補充模塊,可定義為插件。這個就是后來要進行安裝的插件程序。
假設你的程序已經部署在用戶的計算機上,并且能夠正常運行了。但是有一天,用戶打來電話——他們需要增加新的功能。確定了用戶的需求后,你竟然發現原有的軟件架構已經無法勝任新增任務的需求——你需要重新設計這個應用了!但問題是,就算你又用了一個開發周期完成了用戶需要的應用,切不能保證用戶的需求不會再次變更。也就是說,需求蔓延的可能性依然存在。因此,這種情況下插件架構更能顯示出它的優越性。
可以這么說,用它可以帶來方便的地方,而且開發它,也很簡單。而且這樣的主程序根本就不需要改動。需要插件時,拿來就能用,插件更新時,也只需更新這個插件即可。
從程序開發這角度,一般是先開發主程序,決定哪些功能由主程序來完成,然后再建立接口,申明接口的內容,這些內容決定著插件功能的擴展及方向的。這些都是有主程序開發者預先準備好的。插件開發者,從主程序開發者那里得到接口的內容,并書寫繼承這些接口的類,來完成具體的功能。
下面來寫個例子,這個例子沒實際意義,純屬學習思想。例子是網上的經過自己改造的,發現別人某些地方不合理。
首先,新建一個類庫,里面定義接口,這里定義兩個方法,一個有返回值的,一個無返回值的。
using System.Collections.Generic;
using System.Text;
namespace IMsg
{
///<summary>
/// 這是插件必須實現的接口,也是主程序與插件通信的唯一接口
/// 換句話說,主程序只認識插件里的這些方法
///</summary>
publicinterface IMsgPlug
{
void OnShowDlg();
string OnShowInfo();
}
}
將上面的類庫生成IMsg.dll, 新建一個類庫MYPlugin1,添加剛出的引用,分別新建兩個類來實現IMsg中定義的接口。
using System.Collections.Generic;
using System.Text;
using IMsg;
namespace MYPlugin1
{
publicclass myConsole : IMsgPlug
{
#region IMsgPlug 成員
publicvoid OnShowDlg()
{
Console.WriteLine("控制臺調用插件的OnShowDlg方法");
}
publicstring OnShowInfo()
{
return"myConsole";
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using IMsg;
namespace MYPlugin1
{
publicclass MYDlg:Form,IMsgPlug
{
#region IMsgPlug 成員
publicvoid OnShowDlg()
{
this.Text ="插件子窗體";
this.ShowDialog();//調用Form的ShowDialog,顯示窗體
}
publicstring OnShowInfo()
{
return"MyDlg";
}
#endregion
}
}
將上面的都生成dll, 生成目錄可以設置為新建exe工程的bin目錄plugins文件夾下。Plugins文件夾是新建的,專門存放插件的。 新建一個 WinForm項目來使用剛才的插件.
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.IO;
using System.Reflection;
namespace MsgBoxMain
{
publicpartialclass FormMain : Form
{
///<summary>
/// 存放插件的集合
///</summary>
private ArrayList plugins =new ArrayList();
public FormMain()
{
InitializeComponent();
}
///<summary>
/// 載入所有插件
///</summary>
privatevoid LoadAllPlugs()
{
//獲取插件目錄(plugins)下所有文件
string[] files = Directory.GetFiles(Application.StartupPath +@"\plugsins");
foreach (string file in files)
{
if (file.ToUpper().EndsWith(".DLL"))
{
try
{
//載入dll
Assembly ab = Assembly.LoadFrom(file);
Type[] types = ab.GetTypes();
foreach (Type t in types)
{
//如果某些類實現了預定義的IMsg.IMsgPlug接口,則認為該類適配與主程序(是主程序的插件)
if (t.GetInterface("IMsgPlug")!=null)
{
plugins.Add(ab.CreateInstance(t.FullName));
listBox1.Items.Add(t.FullName);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
privatevoid btnLoadPlug_Click(object sender, EventArgs e)
{
LoadAllPlugs();
}
//調用插件的方法
privatevoid btnExecute_Click(object sender, EventArgs e)
{
if (this.listBox1.SelectedIndex ==-1) return;
object selObj =this.plugins[this.listBox1.SelectedIndex];
Type t = selObj.GetType();
MethodInfo OnShowDlg = t.GetMethod("OnShowDlg");
MethodInfo OnShowInfo = t.GetMethod("OnShowInfo");
OnShowDlg.Invoke(selObj, null);
object returnValue = OnShowInfo.Invoke(selObj, null);
this.lblMsg.Text = returnValue.ToString();
}
}
}
運行結果:
這里與網上那位原創的仁兄的看法不同(原文鏈接http://blog.csdn.net/jam12315/archive/2008/08/18/2791534.aspx),可供大家討論。
原文有 這樣的一段:
{//調用存儲在動態數組plugins里面的插件對象的OnShowInfo方法
string msgInfo = ((IMsgPlug)plugins[ListItems.SelectedIndex]).OnShowInfo();
MessageBox.Show(msgInfo, "MYPlugin1", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
elseif (itemStr =="MYDlg")//調用存儲在動態數組plugins里面的插件對象的OnShowDlg方法
{
((IMsgPlug)plugins[ListItems.SelectedIndex]).OnShowDlg();
}
我認為既然是插件,就應該是動態加載的,客戶端肯定不能判斷 itemStr, 因為實現接口的類是不可預料的,因此主程序不應該添加對IMsg的引用,也不應該在客戶端實例化插件對象,因為插件開發的初衷是為了以后更新的時候不更改主程序,只提供對應的dll 下載,就可以直接使用了,以前的接口都定義好了,新的實現類也就是不可預料的,因此不能在主程序實例化實現接口的類,這樣違背了插件的初衷。