代码文件在此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示例的更多相关文章

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

    动态加载与插件系统的初步实现(三):WinForm示例 代码文件在此Download,本文章围绕前文所述默认AppDomain.插件容器AppDomain两个域及IPlugin.PluginProvi ...

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

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

  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. 函数的应用 "注册" and "登录"

    登录 自己写 # 注册 registdef regist(): f = open("account", mode="r+", encoding="ut ...

  2. js实现svg图形转存为图片下载[转]

    我们知道canvas画布可以很方便的js原生支持转为图片格式并下载,但是svg矢量图形则并没有这方面原生的支持.研究过HighChart的svg图形的图片下载机制,其实现原理大体是浏览器端收集SVG代 ...

  3. JavaScript的DOM操作获取元素实际大小

    clientWidth 和 clientHeight 这组属性可以获取元素可视区的大小,可以得到元素内容及内边距所占据的空间大小. 返回了元素大小,但没有单位,默认单位是 px,如果你强行设置了单位, ...

  4. P3565 [POI2014]HOT-Hotels

    题目描述 There are nn towns in Byteotia, connected with only n-1n−1 roads. Each road directly links two ...

  5. Ansible--01

    一.ansible是什么: 类似puppet之类的运维自动化工具 二.为什么选择ansible: 1. ansible是python语言开发的,python语言进入门槛低,方便基于pytnon对ans ...

  6. macbook下 go 语言的 helloworld

    go语言开发的目录 一般go语言$GOPATH 目录约定有三个子目录: src 存放源代码(比如:.go .c .h .s等) pkg 编译后生成的文件(比如:.a) bin 编译后生成的可执行文件( ...

  7. python基础整理3——前端

    html概述和基本结构 html概述 HTML意思是超文本标记语言 html基本结构 一个html的基本结构如下: <!DOCTYPE html> <html lang=" ...

  8. ruby安装及webStorm配置SCSS

    sass安装: 步骤:(window系统) 1.下载RubyInstaller(v2.4.3),运行安装,基本直接next安装,不过有个add to PATH的选项一定要勾选,这样就不用配置环境变量. ...

  9. HDU 2298(纯物理加解一元二次方程)

    Toxophily Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total S ...

  10. oracle常用视图v$mystat v$sesstat v$sysstat v$statname v$thread v$ parameter v$session v$process

    这两天看了盖国强老师的<<深入浅出>>,很佩服盖老师钻研的精神.书中常用到一个查询语句,为了获取当前会话的跟踪文件路径,sql如下: SELECT d.VALUE || '/' ...