zoukankan      html  css  js  c++  java
  • 动态加载与插件系统的初步实现(三):WinForm示例

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

  • 相关阅读:
    【读书笔记】简约至上交互设计四策略目录
    Cassandra在Windows上安装及使用方法[转]
    [转]揭秘全球最大网站Facebook背后的那些软件
    过程改进计划
    制定项目管理计划
    在sublime text3中利用markdown
    ubuntu下更改用户名和主机名
    国庆有感
    最近两天学到的技术汇总
    看见了就当没有看见
  • 原文地址:https://www.cnblogs.com/Jusfr/p/3162611.html
Copyright © 2011-2022 走看看