代碼文件在此Download,本文章圍繞前文所述默認AppDomain、插件容器AppDomain兩個域及IPlugin、PluginProvider、PluginProxy3個類的使用與變化進行。
添加WinForm項目Host、類庫Plugin、引用System.Windows.Forms;的類庫Plugin_A與Plugin_B,其中Plugin_A、Plugin_B的項目屬性中,“生成”選項卡中“輸出路徑”設置為..\Host\bin\Debug\,即指向Host項目的Bin目錄。
考慮到WinForm項目常常涉及多級菜單構建,這里以兩級菜單示例。
Plugin項目中IPlugin代碼:
public interface IPlugin { IList<String> GetMenus(); IList<String> GetMenus(String menu); void Notify(Object userState); }
其中無參方法GetMenus()提取一級菜單,有參重載GetMenus(String menu)提取二級菜單,Notify(Object userState)是兩個應用程序域的通知調用。
PluginProxy繼承MarshalByRefObject,代碼長點:
public class PluginProxy : MarshalByRefObject, IDisposable { private readonly static PluginProxy instance = new PluginProxy(); public static PluginProxy Instance { get { return instance; } } private PluginProxy() { } private AppDomain hostDomain = null; private PluginProvider proxy = null; public PluginProvider Proxy { get { if (hostDomain == null) { hostDomain = AppDomain.CreateDomain("PluginHost"); } if (proxy == null) { Type proxyType = typeof(PluginProvider); proxy = (PluginProvider)hostDomain.CreateInstanceAndUnwrap(proxyType.Assembly.FullName, proxyType.FullName); } return proxy; } } public void Unload() { if (hostDomain != null) { proxy = null; AppDomain.Unload(hostDomain); hostDomain = null; } } public void Dispose() { Unload(); } }
PluginProvider除構造函數外,Notify(IPlugin plugin, Object userState)方法將調用IPlugin插件的Notify方法:
public class PluginProvider : MarshalByRefObject { [ImportMany] public IEnumerable<Lazy<IPlugin>> Plugins { get; set; } public PluginProvider() { AggregateCatalog catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(".")); CompositionContainer container = new CompositionContainer(catalog); container.ComposeParts(this); } public void Notify(IPlugin plugin, Object userState) { plugin.Notify(userState); } }
然后是插件Plugin_A、Plugin_B的實現。添加Plugin類(類名與命名空間隨意)引用System.ComponentModel.Composition,加入[Export(typeof(IPlugin))]修飾。這里使用了一份XML顯示菜單目錄,將在得到通知后將一個Form彈出來:
[Export(typeof(IPlugin))] public class PluginA : MarshalByRefObject, IPlugin { private String menus = @"<Component> <Net> <AuthenticationManager /> <Authorization /> <Cookie /> </Net> <IO> <ErrorEventArgs /> <FileSystemEventArgs /> </IO> </Component>"; public IList<String> GetMenus() { return XElement.Parse(menus).Elements().Select(x => x.Name.LocalName).ToArray(); } public IList<String> GetMenus(String menu) { return XElement.Parse(menus).Elements(menu).Elements().Select(x => x.Name.LocalName).ToArray(); } public void Notify(Object userState) { String text = (String)userState; Label label = new Label() { Text = text, AutoSize = false, Dock = DockStyle.Fill, TextAlign = System.Drawing.ContentAlignment.MiddleCenter, }; Form frm = new Form(); frm.Controls.Add(label); frm.ShowDialog(); } }
Plugin_B與Plugin_A類似,不再重復,然后是Host實現。Host使用了兩個FlowLayoutPanel分別用於顯示一級菜單與兩級菜單。
Load按鈕加載插件列表,將每個插件綁定到一個Button上:
private void button1_Click(object sender, EventArgs e) { flowLayoutPanel1.Controls.Clear(); textBox1.AppendText("PluginProvider loaded"); textBox1.AppendText(Environment.NewLine); PluginProvider proxy = PluginProxy.Instance.Proxy; IEnumerable<Lazy<IPlugin>> plugins = proxy.Plugins; foreach (var plugin in plugins) { foreach (var menu in plugin.Value.GetMenus()) { Button menuBtn = new Button(); menuBtn.Text = menu; menuBtn.Tag = plugin.Value; menuBtn.Click += menuBtn_Click; flowLayoutPanel1.Controls.Add(menuBtn); } } } private void menuBtn_Click(object sender, EventArgs e) { flowLayoutPanel2.Controls.Clear(); Button menuBtn = (Button)sender; try { IPlugin plugin = (IPlugin)menuBtn.Tag; foreach (var item in plugin.GetMenus(menuBtn.Text)) { Button itemBtn = new Button(); itemBtn.Text = item; itemBtn.Tag = plugin; itemBtn.Click += itemBtn_Click; flowLayoutPanel2.Controls.Add(itemBtn); } } catch (AppDomainUnloadedException) { textBox1.AppendText("Plugin domain have been uloaded"); textBox1.AppendText(Environment.NewLine); } } private void itemBtn_Click(object sender, EventArgs e) { try { Button menuBtn = (Button)sender; IPlugin plugin = (IPlugin)menuBtn.Tag; PluginProvider proxy = PluginProxy.Instance.Proxy; proxy.Notify(plugin, menuBtn.Text); } catch (AppDomainUnloadedException) { textBox1.AppendText("Plugin domain not loaded"); textBox1.AppendText(Environment.NewLine); } }
Unload按鈕卸載插件AppDomain:
private void button2_Click(object sender, EventArgs e) { PluginProxy.Instance.Unload(); textBox1.AppendText("PluginProvider unloaded"); textBox1.AppendText(Environment.NewLine); }
Delete按鈕移除Plugin_A.dll、Plugin_B.dll:
private void button3_Click(object sender, EventArgs e) { try { String[] pluginPaths = new[] { "Plugin_A.dll", "Plugin_B.dll" }; foreach (var item in pluginPaths) { if (System.IO.File.Exists(item)) { System.IO.File.Delete(item); textBox1.AppendText(item + " deleted"); } else { textBox1.AppendText(item + " not exist"); } textBox1.AppendText(Environment.NewLine); } } catch (Exception ex) { textBox1.AppendText(ex.Message); textBox1.AppendText(Environment.NewLine); } }
運行結果如下:
我嘗試比較IEnumerable<Lazy<IPlugin>>與IEnumerable<IPlugin>的進程內存占用,在一個額外的Button里進行100加載與卸載,統計內存變化圖如下,有興趣的可以下載EXCEL文件看看:
附求職信息:目前在北京,尋求.Net相關職位,偏向后端,請郵件jusfr.v#gmail.com,替換#為@,溝通后奉上簡歷。