插件框架
- 界面由插件组成
- 算法由插件组成
- 所有插件自己决定自己的位置和能力
- 所有插件可以加载和卸载
前期准备:
插件架构,无非就是通过读取DLL,得到类型,然后,通过反射得到相应实例。
我现在只知道一种方式就是:ass=Assembly.LoadFile(file); ass.GetTypes();得到相应类及实例。好像在博客园看到有人说有其他方法。不知道有没有人告诉我下。
插件要加载,卸载。有2种方式。
1.是通过把加载的类放在另一个应用程序域中,通过线程来实现主应用程序域和加载应用程序员之间通信。然后,通过实现应用程序域的加载和卸载,来达到插件的加载和卸载。
2.把插件里的类,通过创建和销毁,来实现插件的加载和卸载。
我选择了第二种,因为,简单。第一种我觉得代价太大,还有就是不敢把握。
插件怎么决定自己的能力和位置?
我现在,只做了把界面插件化,留了相应位置给算法,或者叫模型(model)。我想,界面控件,有一个父控件去盛放他;模型也有一个东西去调用他。所以我在插件基类basePlugin里定义2个属性:fatherName和Father;fatherName是写插件的时候得到的,Father是在加载插件的时候主程序去查的。也就是说,插件知道自己爸爸的名字,然后主程序就去给你把爸爸找来。
界面和model是2个不同的方式。我为了区分,用了2种方式,一个是标签,用了个叫“PluginType”的特性(Attribute)。主要用来判断改怎么去调他们的构造函数(他们都没有无参构造函数)。在basePlugin里面还定义一个属性plugin_Type,用来去那个地方找爸爸,或者还是其他什么的。反正,肯定要能通过对象,就知道对象的类型。
最后就是插件和主程序不能直接有依赖关系。
所以定义了三个程序集,一个是主程序,一个是插件程序集,一个就是纽带或者叫契约。
契约里定义,插件的基类,及各自类型的插件基类。主程序引用它,知道插件是什么样的,怎么调用。插件程序集也引用他,然后通过继承和主程序搭上关系。
下面是源代码:
先是基类
public class basePlugin { string m_pluginName; public basePlugin(string pluginName) { m_pluginName = pluginName; } public string PluginName { get { return m_pluginName; } } public PluginType plugin_Type { get; set; } public virtual object Father { get; set; } public string fatherName { get; set; } /// <summary> /// 加载 /// </summary> public virtual void load() { } /// <summary> /// 卸载 /// </summary> public virtual void unload() { } }
用于区别的插件的特性:
[global::System.AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] public sealed class PluginTypeAttribute : Attribute { // See the attribute guidelines at // http://go.microsoft.com/fwlink/?LinkId=85236 readonly string m_type; // This is a positional argument public PluginTypeAttribute(string type) { this.m_type = type; } public string Type { get { return m_type; } } // This is a named argument public int NamedInt { get; set; } }
这里我设置的Inherited为true,AllowMutiple为假。是为了保证一个中间的契约里定义插件的分类,而不是,学具体的插件的时候,去定义;还有就是保证一个插件只有一个类别。
下面是Button类别的插件基类:
[PluginType("Control")] public class ButtonPlugin:basePlugin { private InControl m_control; private Control m_FatherControl; private string m_xmlFile; public ButtonPlugin(string name,string xmlfile) : base(name) { m_control = new InControl(); m_control.Run = run; m_xmlFile = xmlfile; setControlAttribute(m_xmlFile); } ……… public override object Father { get { return m_FatherControl; } set { m_FatherControl = value as Control; } } protected virtual void run() { } public sealed override void load() { if (m_FatherControl==null) { throw new ArgumentNullException("请先设置Father属性设置为父控件,在使用load"); } base.load(); m_FatherControl.Controls.Add(m_control); } public sealed override void unload() { base.unload(); m_FatherControl.Controls.Remove(m_control); m_control.Dispose(); } private class InControl : Button { public Action Run { set; get; } protected override void OnClick(EventArgs e) { base.OnClick(e); Run.Invoke(); } }
解释下,我用了嵌套类来写各类插件的基类。这样就类似于多继承,保证了我能把各自功能和控件,都能写成插件。在控件里,我没有选择事件而是选择了重载Onlick。是为了防止事件产生的内存泄漏。把内部类设置为私有,并且调用外部的虚拟方法,是为了不让子类去关心父类的是不是嵌套类。大家在留心下,load()和unload()方法。这里就是这类插件自己去描述该怎么产生,怎么去和爸爸发生关系以及怎么离开爸爸,释放自己的资源。
控件有很多属性是必须要描述。我这使用了xml来描述。
<?xml version="1.0" encoding="utf-8" ?> <Button name="Button1" Location="{197,71}" Size="{75,23}" TabIndex="1" Text="我是插件" UseVisualStyleBackColor="true" fatherName="MainForm"> </Button>
这里请留心下fatherName,插件必须要知道爸爸的名字。ButtonPlugin代码中省略的部分就是xml解析。
在这里想大家请教下,怎么能快速得到一个控件的xml,最好能是WPF那样能够互操作的xml。这个xml是手写的。希望有知道的前辈能不吝赐教。
下面就是插件的管理类,负责管理,加载,卸载插件,并给插件找爸爸。
sealed class Pluginlist { private static Pluginlist m_intance; private Dictionary<string, basePlugin> m_plugins = new Dictionary<string, basePlugin>(); private Dictionary<string, string> m_puginFiles = new Dictionary<string, string>(); private Dictionary<string, Control> m_ControlsManager = new Dictionary<string, Control>(); private string m_app_path; private MainForm m_app; private Pluginlist() { m_app_path = Directory.GetCurrentDirectory() ; string fileName; string[] dllFiles = Directory.GetFiles(m_app_path + @".\plugins", "*.dll"); foreach (string item in dllFiles) { fileName = Path.GetFileName(item).TrimEnd(new char[] { '*','.','d','l','l'}); m_puginFiles.Add(fileName, item); } } 。。。。 public void LoadPlugin(string pluginName) { if (m_plugins.ContainsKey(pluginName)) { return; } string file=m_puginFiles[pluginName]; Assembly ass = Assembly.LoadFile(file); basePlugin plugin = GetPluginIntance(ass,pluginName); switch (plugin.plugin_Type) { case PluginType.Control: plugin.Father = m_ControlsManager[plugin.fatherName]; plugin.load(); break; case PluginType.model: break; default: break; } m_plugins.Add(plugin.PluginName, plugin); } public void unLoadPlugin(basePlugin plugin) { if (!m_plugins.ContainsValue(plugin)) { return; } plugin.unload(); m_plugins.Remove(plugin.PluginName); } public void unLoadPlugin(string pluginName) { if (!m_plugins.ContainsKey(pluginName)) { return; } m_plugins[pluginName].unload(); m_plugins.Remove(pluginName); } public basePlugin GetPluginIntance(Assembly ass,string Name) { foreach (Type type in ass.GetTypes()) { if (!type.IsClass) { continue; } if (!type.IsPublic) { continue; } if (type.BaseType.BaseType != typeof(basePlugin)) { continue; } try { PluginTypeAttribute[] PluginTypeAtrrs = (PluginTypeAttribute[])type.GetCustomAttributes(typeof( PluginTypeAttribute), true); switch (PluginTypeAtrrs[0].Type) { case "Control": return TypeToIntance(type, PluginType.Control,Name); case "Model": return TypeToIntance(type, PluginType.model,Name); default: return null; } } catch (MissingMethodException) { return null; } } new ArgumentNullException("程序集中未找到插件!"); return null; } private basePlugin TypeToIntance(Type plugin ,PluginType pluginType,String name) { Type[] paramType; ConstructorInfo constructor; switch (pluginType) { case PluginType.Control:paramType=new Type[2]; paramType[0] = typeof(String); paramType[1] = typeof(String); constructor = plugin.GetConstructor(paramType); String param = m_app_path + @".\XMLFiles"; return (basePlugin)constructor.Invoke(new string[]{name ,param}); case PluginType.model: return null; default: return null; } } 。。。。。。
解释下m_plugins 是所有已经加载的插件。m_puginFiles 是插件文件夹下存在的插件路径。m_ControlsManager 是所以已经存在的控件集合。m_puginFiles 为了以后能有一个插件配置界面。我在主窗体里,重载了OnControlAdded() 和OnControlRemoved()。所以m_ControlsManager 能得到所以加载到主窗体的控件。但是我还没想好,如果是一个容器控件做成插件。容器控件上的插件该怎么处理。现在,有2条思路,一条是能不能通过扩展方法,改变window的Control类;另一条是在加载容器控件的时候,通过一种方式告诉Pluginlist。
下面,是我写的一个Button插件代码很简单。
public class Button1 : ButtonPlugin { public Button1(string name, string file):base(name,file) { } protected override void run() { base.run(); MessageBox.Show("YY,你大爷!"); } }
YY是我同学,是他让我去写这个DEMO的。
后记:不知道博客存在后记不?第一次技术博客,用了很多口水话,希望大家自动忽略。确实还没毕业很多都不太懂,里面问了不少问题,都快成博问了。希望大家谅解,并能提供指导。
源代码:
https://files.cnblogs.com/tianfeixiang/plugin_demoV1.0001.zip