插件编程小窥
我们在前面的文章中提到了两中实现晚绑定的方式,那么在用的时候是否发现有什么不同呢?
是的,我们会很容易的发现Activator.CreateInstance()创建的对象,我们只能访问他的访问级别为public的方法,但是我们仅仅小小的统一下手脚,使用BindingFlags.NonPublic|BindingFlags.Instance就可以获得该对象的internal,private级别的变量。但是往往后一种方式我们也是不经常采取的,因为我们需要稳定的成员支持,而私有类型的成员往往是程序中最容易更新的,假如一个程序集更新后,我们使用这种方式调用即会发生错误。
今天刚刚过完五一长假回学校(自己给自己放了十天假,哈哈),今天就和同学们一起学习一下插件编程,既然上面我提到了基本安全(稳定)的方式,就一定会用到Activator.CreateInstance,今天我在博客里一共做两个试验,一个是Winfrom的插件体系的构建,一个是控制台的插件体系的构建,当然都是很简单的那种。
1.Winfrom小实验(实验要求:)
- 动态的加载菜单项
- 动态的绑定菜单单击事件
现在我们来整理一下思路:动态的加载当然需要使用反射,前面的文章我们提到了先要加载程序集,然后获得程序集的相关类型的对象,通过对象来访问相应的方法,现在我们可以确定应该是主程序(我在这里称之为宿主程序)下载插件程序集,但是问题来了,即便是加载了我们又怎么去约束我们要实用的插件方法呢。难道所有的插件方法都需要吗?当然不是,这时候我们就用到了插件接口来约束,也就是为插件程序来制定调用规则。
现在我们已经有了个大概思路,改程序该分为三部分:宿主程序,约定接口,插件程序。
为了我们在今后可以编译管理我们将一些有关管理插件的程序集都已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功能:
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功能:
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 //插件的方法 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.编写插件
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 }
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 }
效果图:
从上面两个程序我们可以分析出来,是否能开发插件、如何开发插件是由写主程序的人来决定的