动态加载与插件系统的初步实现(二):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<Lazy<IPlugin>> 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<IPlugin> 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,替换#为@,沟通后奉上简历。