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

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

转载于:https://www.cnblogs.com/Jusfr/p/3162611.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值