插件框架
- 界面由插件组成
- 算法由插件组成
- 所有插件自己决定自己的位置和能力
- 所有插件可以加载和卸载
前期准备:
插件架构,无非就是通过读取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

浙公网安备 33010602011771号