0%

动态加载与插件系统的初步实现(二):AppDomain卸载与代理 - Jusfr - 博客园

Excerpt

前一篇文章简单展示了类型发现和MEF使用,本文初步进入AppDomain相关内容。CLR程序运行时会创建默认程序集容器即AppDomain,默认AppDomain不支持卸载其程序集,但CLR支持创建和卸载AppDomain,这意味着我们可以间接地通过额外的AppDomain实现插件的热插拔。代理Ap


前一篇文章简单展示了类型发现和MEF使用,本文初步进入AppDomain相关内容。

CLR程序运行时会创建默认程序集容器即AppDomain,默认AppDomain不支持卸载其程序集,但CLR支持创建和卸载AppDomain,这意味着我们可以间接地通过额外的AppDomain实现插件的热插拔。

代理AppDomain创建PluginProvider实例,该实例及其发现的IPlugin的实现需要被被默认AppDomain访问,于是发生了跨AppDomain边界的访问,PluginProvider及IPlugin的具体实现需要由MarshalByRefObject派生(更多相关内容仍然需要自行MSDN)。

为Plugin项目添加PluginProxy类,该类负责维护上述的额外AppDomain、将PluginProvider封送回默认AppDomain。本例仅设计了单个插件容器的场景,故以单例模式实现:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<span>public</span> <span>class</span><span> PluginProxy
{
</span><span>private</span> <span>readonly</span> <span>static</span> PluginProxy instance = <span>new</span><span> PluginProxy();

</span><span>private</span><span> PluginProxy()
{
}

</span><span>public</span> <span>static</span><span> PluginProxy Instance
{
</span><span>get</span> { <span>return</span><span> instance; }
}

</span><span>private</span> AppDomain pluginDomain = <span>null</span><span>;
</span><span>private</span> PluginProvider pluginProvider = <span>null</span><span>;

</span><span>public</span><span> PluginProvider Provider
{
</span><span>get</span><span>
{
</span><span>if</span> (pluginDomain == <span>null</span><span>)
{
pluginDomain </span>= AppDomain.CreateDomain(<span>"</span><span>PluginDomain</span><span>"</span><span>);
Type pluginProviderType </span>= <span>typeof</span><span>(PluginProvider);
pluginProvider </span>=<span> (PluginProvider)pluginDomain.CreateInstanceAndUnwrap(pluginProviderType.Assembly.FullName, pluginProviderType.FullName);
}
</span><span>return</span><span> pluginProvider;
}
}

</span><span>public</span> <span>void</span><span> Unload()
{
</span><span>if</span> (pluginDomain != <span>null</span><span>)
{
AppDomain.Unload(pluginDomain);
pluginDomain </span>= <span>null</span><span>;
}
}
}</span>

复制代码

AppDomain的创建与跨边界访问对象的成本很高,后文中默认AppDomain与插件的交互将以代理PluginProxy通知PluginProvider的方式实现。Plugin中PluginProvider小幅修改:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<span>public</span> <span>class</span><span> PluginProvider : MarshalByRefObject
{
[ImportMany]
</span><span>public</span> IEnumerable&lt;Lazy&lt;IPlugin&gt;&gt; Plugins { <span>get</span>; <span>private</span> <span>set</span><span>; }

</span><span>public</span><span> PluginProvider()
{
AggregateCatalog catalog </span>= <span>new</span><span> AggregateCatalog();
catalog.Catalogs.Add(</span><span>new</span> DirectoryCatalog(<span>"</span><span>.</span><span>"</span><span>));
CompositionContainer container </span>= <span>new</span><span> CompositionContainer(catalog);
container.ComposeParts(</span><span>this</span><span>);
}
}</span>

复制代码

MyPlugin1中Plugin1小幅修改:

复制代码

1
2
3
4
5
6
7
8
[Export(<span>typeof</span><span>(IPlugin))]
</span><span>public</span> <span>class</span><span> Plugin1 : MarshalByRefObject, IPlugin
{
</span><span>public</span><span> String DoStuff()
{
</span><span>return</span> <span>"</span><span>MyPlugin1 Plugin1.DoStuff</span><span>"</span><span>;
}
}</span>

复制代码

主程序PluginProxy由静态类属性访问,同时加入逻辑检验DLL可否在AppDomain卸载后删除,WinForm与WCFRestService示例在后续给出。代码文件

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<span>class</span><span> Program
{
</span><span>static</span> <span>void</span> Main(<span>string</span><span>[] args)
{
PluginProvider pluginProvider </span>=<span> PluginProxy.Instance.Provider;
</span><span>foreach</span> (Lazy&lt;IPlugin&gt; plugin <span>in</span><span> pluginProvider.Plugins)
{
Console.WriteLine(plugin.Value.DoStuff());
}
PluginProxy.Instance.Unload();

String filename </span>= <span>"</span><span>MyPlugin1.dll</span><span>"</span><span>;
</span><span>if</span><span> (File.Exists(filename))
{
File.Delete(filename);
Console.WriteLine(</span><span>"</span><span>File deleted</span><span>"</span><span>);
}
</span><span>else</span><span>
{
Console.WriteLine(</span><span>"</span><span>File not exist</span><span>"</span><span>);
}
Console.WriteLine(</span><span>"</span><span>Press Enter to exit</span><span>"</span><span>);
Console.ReadLine();
}
}</span>

复制代码

附求职信息:目前在北京,寻求.Net相关职位,偏向后端,请邮件jusfr.v#gmail.com,替换#为@,沟通后奉上简历。