前言
面对不断变化的需求,我们的软件需要不断升级。应对这样一种变化,我们难道需要天天改我们的代码?看看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#的插件模式只是我的一种实现方式,不一定是最好,但是代表着一种探索吧!