C#实现插件的模式
前言
面对不断变化的需求,我们的软件需要不断升级。应对这样一种变化,我们难道需要天天改我们的代码?看看Eclipse的实现,看看FireFox,他们貌似主程序都是不会变的,而是加载了许多的插件。所谓插件,不就是添加的代码吗?可是为什么我们的C#程序无法做到这点,无法在编译成exe后能继续扩展?本文就向大家介绍一种C#实现插件的模式。
大体设计
对于这样的扩展,我决定使用dll作为我的扩展包。然后让程序自动搜索指定目录下的dll文件,调用dll文件中的函数实现升级和扩展。
大体的流程图如下:
详细设计
不难发现,如果我们直接使用C#调用dll,即使我们找到了dll文件,也没法知道里面的函数叫什么名字,即使可以枚举出来,也没法智能的调用里面的函数,实现我们预期的功能扩展。于是我们犯难了,我们已经写好的程序哪能预料以后会调用哪些dll的哪些函数呢?
其实这个并不复杂,我们可以利用接口的技术实现这样一种功能。所谓接口,就是一份协议,当大家编写dll时都遵守这样一个协议,那么我们写的dll就可以方便的被exe调用。
接口里面定义了所有exe可能用到的方法,并且通过文档的形式规范化这些方法的使用过程,和对我们程序将要照成的影响。接口可以说是我们抽象出来的一些概念,只要是实现了这些概念化的东西的dll都可以被我们的exe拿来使用。因为exe明确知道了dll中会被使用的函数的名称和意义。这样的设计需要我们事先将我们的接口定义的完善。完善的接口会使我们的程序变得更加具有扩展性。
具体实现
具体的代码我分成了三大块。调用的exe,扩展的dll和我们定义的协议dll。
首先看我写的协议dll:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; namespace ControlInterface { public interface IMyControl { event ChangeEventHandle OnChange; string IName { get; } } public delegate void ChangeEventHandle(Control c, string msg); }这里我们定义了一个控件的名称IName和控件的一个事件OnChange。这样我们以后扩展的所有控件只要实现了这样一个接口,我们就可以调用他们的IName活动他们的名字,然后给他们的OnChange事件添加具体的方法。
然后看看我写的扩展用的控件dll:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; using ControlInterface; namespace ControlInterface { public partial class D1 : UserControl,IMyControl { public string name="D1"; public ChangeEventHandle ButtonClick; public D1() { InitializeComponent(); } public string IName { get { return name; } } #region IMyControl 成员 event ChangeEventHandle IMyControl.OnChange { add { ButtonClick += value; } remove { ButtonClick -= value; } } #endregion private void button1_Click(object sender, EventArgs e) { ButtonClick(this, "Yes"); } } }
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; using ControlInterface; namespace ControlInterface { public partial class D2 : UserControl,IMyControl { public string name="D2"; public event ChangeEventHandle TxtChange; public D2() { InitializeComponent(); } public string IName { get { return name; } } #region IMyControl 成员 event ChangeEventHandle IMyControl.OnChange { add { TxtChange += value; } remove { TxtChange -= value; } } #endregion private void textBox1_TextChanged(object sender, EventArgs e) { TxtChange(this, textBox1.Text); } } }
这里实现了D1和D2两个控件。这两个控件将生成一个dll。我们可以称为控件库。
最后是我写的调用的exe:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Reflection; using ControlInterface; namespace DynamicControl { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { Assembly assembly = Assembly.LoadFrom(@"ControlLib.dll"); Type[] types = assembly.GetTypes(); foreach (Type type in types) { BindingFlags bflags =BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ; if (type.GetInterface("IMyControl")!=null) { Object obj = type.InvokeMember("", bflags | BindingFlags.CreateInstance, null, null, null); System.Windows.Forms.Control control = (Control)obj; IMyControl iControl =obj as IMyControl; iControl.OnChange += new ChangeEventHandle(iControl_OnChange); TabPage tp = new TabPage(iControl.IName); tabControl1.TabPages.Add(tp); tp.Controls.Add(control); } } } void iControl_OnChange(Control c, string msg) { MessageBox.Show(msg); } } }
这里通过反射机制动态的加载了这两个控件库里面的控件,并且通过我们定义好的接口,方便的访问到了控件库里面的控件。实现了我们需要的控件扩展。
尾声
这样一种C#的插件模式只是我的一种实现方式,不一定是最好,但是代表着一种探索吧!