代码文件在此Download,本文章围绕前文所述默认AppDomain、插件容器AppDomain两个域及IPlugin、PluginProvider、PluginProxy3个类的使用与变化进行。
添加WinForm项目Host、类库Plugin、引用System.Windows.Forms;的类库Plugin_A与Plugin_B,其中Plugin_A、Plugin_B的项目属性中,“生成”选项卡中“输出路径”设置为..HostinDebug,即指向Host项目的Bin目录。
考虑到WinForm项目常常涉及多级菜单构建,这里以两级菜单示例。
Plugin项目中IPlugin代码:
public interfaceIPlugin { IList<String>GetMenus(); IList<String>GetMenus(String menu); voidNotify(Object userState); }
其中无参方法GetMenus()提取一级菜单,有参重载GetMenus(String menu)提取二级菜单,Notify(Object userState)是两个应用程序域的通知调用。
PluginProxy继承MarshalByRefObject,代码长点:
public classPluginProxy : MarshalByRefObject, IDisposable { private readonly static PluginProxy instance = newPluginProxy(); public staticPluginProxy Instance { get { returninstance; } } privatePluginProxy() { } private AppDomain hostDomain = null; private PluginProvider proxy = null; publicPluginProvider 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); } returnproxy; } } public voidUnload() { if (hostDomain != null) { proxy = null; AppDomain.Unload(hostDomain); hostDomain = null; } } public voidDispose() { Unload(); } }
PluginProvider除构造函数外,Notify(IPlugin plugin, Object userState)方法将调用IPlugin插件的Notify方法:
public classPluginProvider : MarshalByRefObject { [ImportMany] public IEnumerable<Lazy<IPlugin>> Plugins { get; set; } publicPluginProvider() { AggregateCatalog catalog = newAggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(".")); CompositionContainer container = newCompositionContainer(catalog); container.ComposeParts(this); } public voidNotify(IPlugin plugin, Object userState) { plugin.Notify(userState); } }
然后是插件Plugin_A、Plugin_B的实现。添加Plugin类(类名与命名空间随意)引用System.ComponentModel.Composition,加入[Export(typeof(IPlugin))]修饰。这里使用了一份XML显示菜单目录,将在得到通知后将一个Form弹出来:
[Export(typeof(IPlugin))] public classPluginA : 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 voidNotify(Object userState) { String text =(String)userState; Label label = newLabel() { Text =text, AutoSize = false, Dock =DockStyle.Fill, TextAlign =System.Drawing.ContentAlignment.MiddleCenter, }; Form frm = newForm(); frm.Controls.Add(label); frm.ShowDialog(); } }
Plugin_B与Plugin_A类似,不再重复,然后是Host实现。Host使用了两个FlowLayoutPanel分别用于显示一级菜单与两级菜单。
Load按钮加载插件列表,将每个插件绑定到一个Button上:
private void button1_Click(objectsender, 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 inplugins) { foreach (var menu inplugin.Value.GetMenus()) { Button menuBtn = newButton(); menuBtn.Text =menu; menuBtn.Tag =plugin.Value; menuBtn.Click +=menuBtn_Click; flowLayoutPanel1.Controls.Add(menuBtn); } } } private void menuBtn_Click(objectsender, EventArgs e) { flowLayoutPanel2.Controls.Clear(); Button menuBtn =(Button)sender; try{ IPlugin plugin =(IPlugin)menuBtn.Tag; foreach (var item inplugin.GetMenus(menuBtn.Text)) { Button itemBtn = newButton(); 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(objectsender, 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(objectsender, EventArgs e) { PluginProxy.Instance.Unload(); textBox1.AppendText("PluginProvider unloaded"); textBox1.AppendText(Environment.NewLine); }
Delete按钮移除Plugin_A.dll、Plugin_B.dll:
private void button3_Click(objectsender, EventArgs e) { try{ String[] pluginPaths = new[] { "Plugin_A.dll", "Plugin_B.dll"}; foreach (var item inpluginPaths) { 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文件看看: