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

摘要:
动态加载与插件系统的初步实现(三):WinForm示例代码文件在此Download,本文章围绕前文所述默认AppDomain、插件容器AppDomain两个域及IPlugin、PluginProvider、PluginProxy3个类的使用与变化进行。考虑到WinForm项目常常涉及多级菜单构建,这里以两级菜单示例。PluginProxy继承MarshalByRefObject,代码长点:publicclassPluginProxy:MarshalByRefObject,IDisposable{privatereadonlystaticPluginProxyinstance=newPluginProxy();publicstaticPluginProxyInstance{get{returninstance;}}privatePluginProxy(){}privateAppDomainhostDomain=null;privatePluginProviderproxy=null;publicPluginProviderProxy{get{if{hostDomain=AppDomain.CreateDomain;}if{TypeproxyType=typeof;proxy=hostDomain.CreateInstanceAndUnwrap;}returnproxy;}}publicvoidUnload(){if(hostDomain!=null){proxy=null;AppDomain.Unload;hostDomain=null;}}publicvoidDispose(){Unload();}}PluginProvider除构造函数外,Notify方法将调用IPlugin插件的Notify方法:publicclassPluginProvider:MarshalByRefObject{[ImportMany]publicIEnumerable˂Lazy˃Plugins{get;set;}publicPluginProvider(){AggregateCatalogcatalog=newAggregateCatalog();catalog.Catalogs.Add;CompositionContainercontainer=newCompositionContainer;container.ComposeParts;}publicvoidNotify{plugin.Notify;}}然后是插件Plugin_A、Plugin_B的实现。添加Plugin类引用System.ComponentModel.Composition,加入[Export]修饰。Host使用了两个FlowLayoutPanel分别用于显示一级菜单与两级菜单。
动态加载与插件系统的初步实现(三):WinForm示例

代码文件在此Download,本文章围绕前文所述默认AppDomain、插件容器AppDomain两个域及IPlugin、PluginProvider、PluginProxy3个类的使用与变化进行。

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

添加WinForm项目Host、类库Plugin、引用System.Windows.Forms;的类库Plugin_A与Plugin_B,其中Plugin_A、Plugin_B的项目属性中,“生成”选项卡中“输出路径”设置为..HostinDebug,即指向Host项目的Bin目录。

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

考虑到WinForm项目常常涉及多级菜单构建,这里以两级菜单示例。

Plugin项目中IPlugin代码:

public interfaceIPlugin
{
    IList<String>GetMenus();
    IList<String>GetMenus(String menu);
    voidNotify(Object userState);
}

其中无参方法GetMenus()提取一级菜单,有参重载GetMenus(String menu)提取二级菜单,Notify(Object userState)是两个应用程序域的通知调用。

PluginProxy继承MarshalByRefObject,代码长点:

复制代码
public classPluginProxy : MarshalByRefObject, IDisposable
{
    private readonly static PluginProxy instance = newPluginProxy();
    public staticPluginProxy Instance
    {
        get { returninstance; }
    }
    privatePluginProxy()
    {
    }
    private AppDomain hostDomain = null;
    private PluginProvider proxy = null;
    publicPluginProvider 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);
            }
            returnproxy;
        }
    }
    public voidUnload()
    {
        if (hostDomain != null)
        {
            proxy = null;
            AppDomain.Unload(hostDomain);
            hostDomain = null;
        }
    }
    public voidDispose()
    {
        Unload();
    }
}
复制代码

PluginProvider除构造函数外,Notify(IPlugin plugin, Object userState)方法将调用IPlugin插件的Notify方法:

复制代码
public classPluginProvider : MarshalByRefObject
{
    [ImportMany]
    public IEnumerable<Lazy<IPlugin>> Plugins { get; set; }
    publicPluginProvider()
    {
        AggregateCatalog catalog = newAggregateCatalog();
        catalog.Catalogs.Add(new DirectoryCatalog("."));
        CompositionContainer container = newCompositionContainer(catalog);
        container.ComposeParts(this);
    }
    public voidNotify(IPlugin plugin, Object userState)
    {
        plugin.Notify(userState);
    }
}
复制代码

然后是插件Plugin_A、Plugin_B的实现。添加Plugin类(类名与命名空间随意)引用System.ComponentModel.Composition,加入[Export(typeof(IPlugin))]修饰。这里使用了一份XML显示菜单目录,将在得到通知后将一个Form弹出来:

复制代码
[Export(typeof(IPlugin))]
public classPluginA : 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 voidNotify(Object userState)
    {
        String text =(String)userState;
        Label label = newLabel()
        {
            Text =text,
            AutoSize = false,
            Dock =DockStyle.Fill,
            TextAlign =System.Drawing.ContentAlignment.MiddleCenter,
        };
        Form frm = newForm();
        frm.Controls.Add(label);
        frm.ShowDialog();
    }
}
复制代码

Plugin_B与Plugin_A类似,不再重复,然后是Host实现。Host使用了两个FlowLayoutPanel分别用于显示一级菜单与两级菜单。

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

Load按钮加载插件列表,将每个插件绑定到一个Button上:

复制代码
private void button1_Click(objectsender, 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 inplugins)
    {
        foreach (var menu inplugin.Value.GetMenus())
        {
            Button menuBtn = newButton();
            menuBtn.Text =menu;
            menuBtn.Tag =plugin.Value;
            menuBtn.Click +=menuBtn_Click;
            flowLayoutPanel1.Controls.Add(menuBtn);
        }
    }
}
private void menuBtn_Click(objectsender, EventArgs e)
{
    flowLayoutPanel2.Controls.Clear();
    Button menuBtn =(Button)sender;
    try{
        IPlugin plugin =(IPlugin)menuBtn.Tag;
        foreach (var item inplugin.GetMenus(menuBtn.Text))
        {
            Button itemBtn = newButton();
            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(objectsender, 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(objectsender, EventArgs e)
{
    PluginProxy.Instance.Unload();
    textBox1.AppendText("PluginProvider unloaded");
    textBox1.AppendText(Environment.NewLine);
}

Delete按钮移除Plugin_A.dll、Plugin_B.dll:

复制代码
private void button3_Click(objectsender, EventArgs e)
{
    try{
        String[] pluginPaths = new[] { "Plugin_A.dll", "Plugin_B.dll"};
        foreach (var item inpluginPaths)
        {
            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);
    }
}
复制代码

运行结果如下:
动态加载与插件系统的初步实现(3):WinForm示例第14张

我尝试比较IEnumerable<Lazy<IPlugin>>与IEnumerable<IPlugin>的进程内存占用,在一个额外的Button里进行100加载与卸载,统计内存变化图如下,有兴趣的可以下载EXCEL文件看看:动态加载与插件系统的初步实现(3):WinForm示例第15张

免责声明:文章转载自《动态加载与插件系统的初步实现(3):WinForm示例》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇图像的批处理delphi 利用 InterlockedCompareExchange 实现主线程维一锁等待下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

十九、多文件上传(ajaxFileupload实现多文件上传功能)

来源于https://www.jb51.net/article/128647.htm 打开google 搜索"ajaxFileupload' ‘多文件上传"可以搜到许许多多类似的,那我为什么还要写一下呢?一个是对之前大神的贡献表示感谢;二个是自己知识的总结;三个是自己在原有的基础上改动了下,在此记录,可能帮助其他朋友。 用过这个插件的都知道这个插件的基本用...

spring属性配置执行过程,单列和原型区别

  Spring配置中,采用属性注入时,当创建IOC容器时,也直接创建对象,并且执行相对应的setter方法 Student.java 1 package com.scope; 2 3 public class Student { 4 private String name; 5 private String number;...

windows下创建h2集群,及java集成详细步骤

1.下载h2包,解压 2.cmd操作,进入bin目录 3.创建两个目录 4.建立集群 输入以下命令(需要进入h2的bin目录) java -cp "h2-1.4.195.jar;%H2DRIVERS%;%CLASSPATH%" org.h2.tools.Server -tcpAllowOthers -tcpPort 9101 -webAllowOth...

C#中将字符串转成 Base64 编码(小技巧)

/// <summary>/// /// </summary>/// <param name="Str"></param>/// <returns></returns>public string ToBase64Str(string Str){byte[] b = System.Te...

android环境下的即时通讯

首先了解一下即时通信的概念。通过消息通道 传输消息对象,一个账号发往另外一账号,只要账号在线,可以即时获取到消息,这就是最简单的即使通讯。消息通道可由TCP/IP UDP实现。通俗讲就是把一个人要发送给另外一个人的消息对象(文字,音视频,文件)通过消息通道(C/S实时通信)进行传输的服务。即时通讯应该包括四种形式,在线直传、在线代理、离线代理、离线扩展。在...

JAVA中获取当前执行路径

1)方式一 URI webPathTemp = Thread.currentThread().getContextClassLoader().getResource("").toURI();StringwebPath=String.valueOf(webPathTemp.getPath());webPath = webPath.substring(1,we...