- 界面由插件組成
- 算法由插件組成
- 所有插件自己決定自己的位置和能力
- 所有插件可以加載和卸載
前期准備:
插件架構,無非就是通過讀取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的。
后記:不知道博客存在后記不?第一次技術博客,用了很多口水話,希望大家自動忽略。確實還沒畢業很多都不太懂,里面問了不少問題,都快成博問了。希望大家諒解,並能提供指導。
源代碼:
http://files.cnblogs.com/tianfeixiang/plugin_demoV1.0001.zip