动态加载与插件系统的初步实现(三):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,替换#为@,沟通后奉上简历。
动态加载与插件系统的初步实现(三):WinForm示例的更多相关文章
- 动态加载与插件系统的初步实现(3):WinForm示例
动态加载与插件系统的初步实现(三):WinForm示例 代码文件在此Download,本文章围绕前文所述默认AppDomain.插件容器AppDomain两个域及IPlugin.PluginProvi ...
- 动态加载与插件系统的初步实现(四):解析JSON、扩展Fiddler
按文章结构,这部分应该给出WCFRest项目示例,我想WinForm示例足够详尽了,况且WCFRest还不需要使用插件AppDomain那一套,于是把最近写的Fiddler扩展搬上来吧. Fiddle ...
- 动态加载与插件系统的初步实现(一):反射与MEF解决方案
涉及内容: 反射与MEF解决方案 AppDomain卸载与代理 WinForm.WcfRestService示 PRRT1: 反射实现 插件系统的基本目的是实现宿主与组件的隔离,核心是作为接驳约定的接 ...
- 动态加载与插件系统的初步实现(二):AppDomain卸载与代理
前一篇文章简单展示了类型发现和MEF使用,本文初步进入AppDomain相关内容. CLR程序运行时会创建默认程序集容器即AppDomain,默认AppDomain不支持卸载其程序集,但CLR支持创建 ...
- C# 实现动态加载DLL插件 及HRESULT:0x80131047处理
本代码实现DLL的动态加载, 类似PS里的滤镜插件! 1. 建立一个接口项目类库,此处名称为:Test.IPlugin using System; namespace Test.IPlugin { p ...
- vue动态加载jQuery插件
要先npm安装jQuery插件哦 window.$=$; window.jQuery=$; function loadJs(Url,callback){ var Nscript=document.cr ...
- Java_Java中动态加载jar文件和class文件
转自:http://blog.csdn.net/mousebaby808/article/details/31788325 概述 诸如tomcat这样的服务器,在启动的时候会加载应用程序中lib目录下 ...
- [转载] Java中动态加载jar文件和class文件
转载自http://blog.csdn.net/mousebaby808/article/details/31788325 概述 诸如tomcat这样的服务器,在启动的时候会加载应用程序中lib目录下 ...
- liteos动态加载(十三)
1. 概述 1.1 基本概念 动态加载是一种程序加载技术. 静态链接是在链接阶段将程序各模块文件链接成一个完整的可执行文件,运行时作为整体一次性加载进内存.动态加载允许用户将程序各模块编译成独立的文件 ...
随机推荐
- HTML-head头部浅析
HTML结构 在sublime或HBuildr新建HTML文件,输入html:5,按下tab键后,自动生成的代码大致如下: <!DOCTYPE html> <html lang=&q ...
- c++课程学习(未完待续)
关于c++课程学习 按照计划,我首先阅读谭浩强c++程序设计一书的ppt,发现第一章基本上都是很基础的东西. 同时,书中与班导师一样,推荐了使用visual c++. 而师爷的教程里面推荐使用的是ec ...
- 死磕salt系列-salt文章目录汇总
死磕salt系列-salt入门 死磕salt系列-salt配置文件 死磕salt系列-salt grains pillar 配置 死磕salt系列-salt 常用modules 死磕salt系列-sa ...
- BZOJ1434:[ZJOI2009]染色游戏(博弈论)
Description 一共n×m个硬币,摆成n×m的长方形.dongdong和xixi玩一个游戏,每次可以选择一个连通块,并把其中的硬币全部翻转,但是需要满足存在一个硬币属于这个连通块并且所有其他硬 ...
- 1050. [HAOI2006]旅行【并查集+枚举】
Description 给你一个无向图,N(N<=500)个顶点, M(M<=5000)条边,每条边有一个权值Vi(Vi<30000).给你两个顶点S和T,求 一条路径,使得路径上最 ...
- [luogu3942] 将军令
题面 题目的意思大概是给你一棵n个点的树, 求最少需要多少个多少个点, 整棵树都被覆盖(覆盖的意思是所有离被选中的点距离不大于k的点都视作已覆盖). 考虑贪心(其实我考试的时候以为是道树形dp ...
- Python自动化之session反解案例
session反解案例 from django.contrib.sessions.models import Session sess = Session.objects.get(pk='a92d67 ...
- 多线程并发容器CopyOnWriteArrayList
原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容 ...
- RANSAC与 最小二乘(LS, Least Squares)拟合直线的效果比较
代码下载地址: 1.Matlab版本:http://pan.baidu.com/s/1eQIzj3c.进入目录后,请自行定位到该博客的源代码与数据的目录“
- zabbix 主动模式和被动模式配置文件对比
1.主动模式: 在web上看zabbix available 是红色 [root@python ~]# egrep -v '^#|^$' /etc/zabbix/zabbix_agentd.conf ...