Fork me on GitHub
动态加载与插件系统的初步实现(3):WinForm示例

动态加载与插件系统的初步实现(三):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文件看看:

 

 
 
 
posted on 2013-06-29 21:36  HackerVirus  阅读(225)  评论(0编辑  收藏  举报