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

 
 
 

动态加载与插件系统的初步实现(3):WinForm示例的更多相关文章

  1. 动态加载与插件系统的初步实现(四):解析JSON、扩展Fiddler

    按文章结构,这部分应该给出WCFRest项目示例,我想WinForm示例足够详尽了,况且WCFRest还不需要使用插件AppDomain那一套,于是把最近写的Fiddler扩展搬上来吧. Fiddle ...

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

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

  3. 动态加载与插件系统的初步实现(一):反射与MEF解决方案

    涉及内容: 反射与MEF解决方案 AppDomain卸载与代理 WinForm.WcfRestService示 PRRT1: 反射实现 插件系统的基本目的是实现宿主与组件的隔离,核心是作为接驳约定的接 ...

  4. 动态加载与插件系统的初步实现(二):AppDomain卸载与代理

    前一篇文章简单展示了类型发现和MEF使用,本文初步进入AppDomain相关内容. CLR程序运行时会创建默认程序集容器即AppDomain,默认AppDomain不支持卸载其程序集,但CLR支持创建 ...

  5. C# 实现动态加载DLL插件 及HRESULT:0x80131047处理

    本代码实现DLL的动态加载, 类似PS里的滤镜插件! 1. 建立一个接口项目类库,此处名称为:Test.IPlugin using System; namespace Test.IPlugin { p ...

  6. vue动态加载jQuery插件

    要先npm安装jQuery插件哦 window.$=$; window.jQuery=$; function loadJs(Url,callback){ var Nscript=document.cr ...

  7. Java_Java中动态加载jar文件和class文件

    转自:http://blog.csdn.net/mousebaby808/article/details/31788325 概述 诸如tomcat这样的服务器,在启动的时候会加载应用程序中lib目录下 ...

  8. [转载] Java中动态加载jar文件和class文件

    转载自http://blog.csdn.net/mousebaby808/article/details/31788325 概述 诸如tomcat这样的服务器,在启动的时候会加载应用程序中lib目录下 ...

  9. liteos动态加载(十三)

    1. 概述 1.1 基本概念 动态加载是一种程序加载技术. 静态链接是在链接阶段将程序各模块文件链接成一个完整的可执行文件,运行时作为整体一次性加载进内存.动态加载允许用户将程序各模块编译成独立的文件 ...

随机推荐

  1. Android Notification通知详细解释

    Android Notification通知具体解释  Notification: (一).简单介绍:         显示在手机状态栏的通知. Notification所代表的是一种具有全局效果的通 ...

  2. python元类分析

    刚開始接触到Python新式类中的元类的概念的时候非常是纠结了下..不知道这是个啥东西... 用下面几个定义来说明吧: (1)Python中,类也是对象..仅仅只是这样的对象比較的特殊,他用于创建别的 ...

  3. s2sh三大框架整合过程(仅供参考)

    三大框架顾名思义就是非常有名的Struts2 ,Hibernate,Spring, 框架整合的方法很多,现在我写一个非常简单的整合过程,相信大家一看就会! 这里使用的struts-2.2.1.1.hi ...

  4. Java-DES算法加密解密工具类

    import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import ...

  5. 快速构建Windows 8风格应用15-ShareContract构建

    原文:快速构建Windows 8风格应用15-ShareContract构建 本篇博文主要介绍共享数据包.如何构建共享源.如何构建共享目标.DataTransferManager类. 共享数据包 Da ...

  6. leetcode 34 Search Insert Position

    Given a sorted array and a target value, return the index if the target is found. If not, return the ...

  7. leetcode第24题--Reverse Nodes in k-Group

    problem: Given a linked list, reverse the nodes of a linked list k at a time and return its modified ...

  8. Wijmo 5 + Ionic Framework之:费用跟踪 App

    Wijmo 5 + Ionic Framework之:费用跟踪 App 费用跟踪应用采用了Wijmo5和Ionic Framework创建,目的是构建一个hybird app. 我们基于<Mob ...

  9. IQueryable与IQEnumberable的区别

    IEnumberable接口: 公开枚举器,该枚举器支持在指定类型的集合上进行简单迭代.也就是说:实现了此接口的object,就可以直接使用froeach遍历此object; IQueryable接口 ...

  10. PBKDF2WithHmacSHA1算法

    主要用于明文密码加密字符串存入数据库.由棱镜门思考.目前大部分企业中都是明文密码.一旦被攻破.危害非常大.现在主流加密技术是MD5加密.不过MD5的存在小概率碰撞(根据密码学的定义,如果内容不同的明文 ...