我們在前面的文章中提到了兩中實現晚綁定的方式,那麼在用的時候是否發現有什麼不同呢?
是的,我們會很容易的發現Activator.CreateInstance()創建的對象,我們只能訪問他的訪問級別為public的方法,但是我們僅僅小小的統一下手腳,使用BindingFlags.NonPublic|BindingFlags.Instance就可以獲得該對象的internal,private級別的變量。但是往往後一種方式我們也是不經常采取的,因為我們需要穩定的成員支持,而私有類型的成員往往是程序中最容易更新的,假如一個程序集更新後,我們使用這種方式調用即會發生錯誤。
今天剛剛過完五一長假回學校(自己給自己放了十天假,哈哈),今天就和同學們一起學習一下插件編程,既然上面我提到了基本安全(穩定)的方式,就一定會用到Activator.CreateInstance,今天我在博客裡一共做兩個試驗,一個是Winfrom的插件體系的構建,一個是控制台的插件體系的構建,當然都是很簡單的那種。
1.Winfrom小實驗(實驗要求:)
1. 動態的加載菜單項
2. 動態的綁定菜單單擊事件
現在我們來整理一下思路:動態的加載當然需要使用反射,前面的文章我們提到了先要加載程序集,然後獲得程序集的相關類型的對象,通過對象來訪問相應的方法,現在我們可以確定應該是主程序(我在這裡稱之為宿主程序)下載插件程序集,但是問題來了,即便是加載了我們又怎麼去約束我們要實用的插件方法呢。難道所有的插件方法都需要嗎?當然不是,這時候我們就用到了插件接口來約束,也就是為插件程序來制定調用規則。
現在我們已經有了個大概思路,改程序該分為三部分:宿主程序,約定接口,插件程序。
為了我們在今後可以編譯管理我們將一些有關管理插件的程序集都已XML的方式進行管理,這樣將不會遺漏和便於修改。
1 <Assemblypaths>
2 <oper type="toUpper" path="G:\C#\測試程序集\toUpper.dll"/>
3 <oper type="toLower" path="G:\C#\測試程序集\toLower.dll"/>
4 </Assemblypaths>
在這個Xml文件裡保存著插件實現接口成員的功能以及插件的保存路徑。
到了這裡我們肯定要定義一個約定接口程序集了,具體實現如下:
1 namespace EditPlus
2 {
3 public interface IEditPlus
4 {
5 string Name { get;}
6 string OperEdit(string str);
7 }
8 }
接下來的任務就很簡單了,插件是用來干什麼的,他就是來實現這個約定接口的,現在我們暫時安裝上面xml文件的需求定義2個擴展插件,具體實現如下:
1.toUpper功能:
toUpperFunction
1 using EditPlus;
2
3 namespace letterUpper
4 {
5 public class letterUpper :IEditPlus
6 {
7 public string Name
8 {
9 get
10 {
11 return "全部大寫";
12 }
13 }
14
15 public string OperEdit(string str)
16 {
17 return str.ToUpper();
18 }
19 }
20 }
2.toLower功能:
toLowerFunction
using EditPlus;
namespace toLower
{
public class letterLower:IEditPlus
{
public string Name
{
get { return "全部小寫"; }
}
public string OperEdit(string str)
{
return str.ToLower();
}
}
}
現在我們就來宿主程序中加載插件程序集,動態添加菜單項並綁定相應的方法。
1 using System;
2 using System.Windows.Forms;
3 using System.Reflection;
4 using EditPlus;
5 using System.Xml.Linq;
6 using System.Collections;
7
8 namespace 插件宿主程序
9 {
10 public partial class Form1 : Form
11 {
12 public Form1()
13 {
14 InitializeComponent();
15 }
16
17 private void Form1_Load(object sender, EventArgs e)
18 {
19 //讀取xml文件,獲取要加載的菜單名稱和程序集路徑
20 XDocument doc = XDocument.Load(@"G:\C#\測試程序集\path.xml", LoadOptions.None);
21 IEnumerable Opers = doc.Root.Elements("oper");
22 foreach (XElement oper in Opers)
23 {
24 //加載程序集
25 Assembly a = Assembly.LoadFrom(oper.LastAttribute.Value);
26 Type[] types=a.GetTypes();
27 foreach(Type type in types)
28 {
29 if (!type.IsAbstract && !type.IsNotPublic && typeof(IEditPlus).IsAssignableFrom(type))
30 {
31 IEditPlus idplus = (IEditPlus)Activator.CreateInstance(type);
32 ToolStripMenuItem subItem = new ToolStripMenuItem(idplus.Name);
33 菜單ToolStripMenuItem.DropDownItems.Add(subItem);
34 subItem.Tag = idplus;
35 //綁定事件
36 subItem.Click += new EventHandler(subItem_Click);
37 }
38 }
39 }
40 }
41
42 void subItem_Click(object sender, EventArgs e)
43 {
44 ToolStripMenuItem subItem = (ToolStripMenuItem)sender;
45 IEditPlus idplus = (IEditPlus)subItem.Tag;
46 richTextBox1.Text=idplus.OperEdit(richTextBox1.Text);
47 }
48 }
49
50 }
運行效果:
________________________________________
通過上面的例子我們已經知道插件是怎麼回事了,現在我們就來正規的定義一下插件。
插件(plugin):是指遵循一定的接口規范,可以動態的加載和運行的程序模塊。從上面的例子可以看出,利用反射動態加載程序集的能力,可以加載和實現插件的功能,實現插件的功能的所有類必須實現定義插件的接口。
下面我們在控制台實現一個小插件的開發。
基本思路和上面的差不多,只是上面的程序我們使用了同一個接口來規范,現在我們對於插件和宿主程序使用不同的接口來分別規范一下,看看有什麼改進。
代碼簡單,就不在說什麼實現要求,一看便明白了。
1.首先定義一個插件接口
插件接口
1 namespace PluginInterface
2 {
3 public interface IPlugin
4 {
5 //插件名稱
6 string Name { get;}
7 //插件的方法 www.2cto.com
8 object Todo(object parameter);
9 }
10 }
2.定義一個宿主接口
宿主接口
1 using System.Collections.Generic;
2 using PluginInterface;
3
4 namespace HostInterface
5 {
6 public interface IHost
7 {
8 //獲得已加載的插件集合
9 List<IPlugin> Plugins {get ;}
10 //裝載所有實現了IPlugin接口的插件,path為存放目錄
11 int LoadPlugin(string path);//返回加載插件的數量、
12 //獲取指定的插件
13 IPlugin getPlugin(string Plugin_name);
14 }
15 }
3.編寫插件
Plugin1
1 using System;
2 using PluginInterface;
3
4 namespace Plugin1
5 {
6 public class Plugin1 : IPlugin
7 {
8 private string name;
9 public string Name
10 {
11 get { return name; }
12 }
13 public Plugin1()
14 {
15 name = "Plugin1";
16 }
17 public object Todo(object parameter)
18 {
19 Console.WriteLine(name+" todo: "+parameter.ToString());
20 return parameter;
21 }
22 }
23 }
Plugin2
1 using System;
2 using PluginInterface;
3
4 namespace Plugin2
5 {
6 public class Plugin2 : IPlugin
7 {
8 private string name;
9 public string Name
10 {
11 get { return name; }
12 }
13 public Plugin2()
14 {
15 name = "Plugin2";
16 }
17 public object Todo(object parameter)
18 {
19 Console.WriteLine(name + " todo: " + parameter.ToString());
20 return parameter;
21 }
22 }
23 }
4.主程序模塊
1 using System;
2 using System.Collections.Generic;
3 using HostInterface;
4 using PluginInterface;
5 using System.IO;
6 using System.Reflection;
7
8 namespace 插件編程測試
9 {
10 class Host:IHost
11 {
12 //用來存放已加載的插件集合
13 private List<IPlugin> plugins = new List<IPlugin>();
14 static void Main(string[] args)
15 {
16 //創建Host對象
17 Host host = new Host();
18 //加載插件
19 host.LoadPlugin(@"G:\C#\測試程序集");
20 //顯示所有已加載的插件
21 Console.WriteLine("所有已加載的插件:");
22 int i = 1;
23 foreach (IPlugin ip in host.plugins)
24 {
25 Console.WriteLine("{0}--{1}",i++,ip.Name);
26 }
27 //選擇指定的插件
28 int index = InputNumber("請選擇一個插件並確認");
29 //執行插件的功能
30 IPlugin plugin = host.getPlugin(host.plugins[index-1].Name);
31 plugin.Todo("完成測試!");
32 Console.ReadKey();
33 }
34 private static int InputNumber(string prompt)
35 {
36 Console.Write(prompt);
37 string s = Console.ReadLine();
38 int count = 0;
39 try
40 {
41 count = Int32.Parse(s);
42 }
43 catch (Exception ex)
44 {
45 Console.WriteLine(ex.Message);
46 }
47 return count;
48 }
49 public List<PluginInterface.IPlugin> Plugins
50 {
51 get { return plugins;}
52
53 }
54
55 public int LoadPlugin(string path)
56 {
57 string[] dll_Files=Directory.GetFiles(path, "*.dll");
58 //判斷每個程序集是否實現了插件接口
59 foreach (string file in dll_Files)
60 {
61 Assembly a = Assembly.LoadFrom(file);
62 //檢查程序的公開接口是否是類,是否實現了IPlugin接口
63 foreach (Type t in a.GetExportedTypes())
64 {
65 if (t.IsClass && typeof(IPlugin).IsAssignableFrom(t))
66 {
67 //如果是則創建實現了Iplugin接口的對象
68 IPlugin plugin=Activator.CreateInstance(t) as IPlugin;
69 //添加到集合中
70 Plugins.Add(plugin);
71 }
72 }
73 }
74 return plugins.Count;//返回合格插件的數目
75 }
76
77 /// <summary>
78 /// 返回指定的插件
79 /// </summary>
80 /// <param name="Plugin_name">插件名</param>
81 /// <returns>插件</returns>
82 public IPlugin getPlugin(string Plugin_name)
83 {
84 foreach (IPlugin ip in plugins)
85 {
86 if (ip.Name == Plugin_name)
87 {
88 return ip;
89 }
90 }
91 return null;
92 }
93 }
94 }
效果圖:
從上面兩個程序我們可以分析出來,是否能開發插件、如何開發插件是由寫主程序的人來決定的
作者 謝舸哥