动态加载与插件系统的初步实现(三):WinForm示例

来源:互联网 发布:非洲有网络吗 编辑:程序博客网 时间:2024/05/29 04:54

代码文件在此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文件看看:


<script type="text/javascript"><!--google_ad_client = "ca-pub-1944176156128447";/* cnblogs 首页横幅 */google_ad_slot = "5419468456";google_ad_width = 728;google_ad_height = 90;//--></script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
原创粉丝点击