代码文件在此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,替换#为@,沟通后奉上简历。