本文共 2225 字,大约阅读时间需要 7 分钟。
为自己写的程序添加插件真的是一个相当常见的功能,然而如果只是简单加载程序集然后去执行程序集中的代码,会让宿主应用程序暴露在非常危险的境地!因为只要插件能够运行任何一行代码,就能将宿主应用程序修改得天翻地覆哭爹喊娘;而根本原因,就在于暴露了整个托管堆和整个 UI 树。
如果将宿主和插件放到不同的应用程序域中,则可以解决此问题。本文将介绍跨应用程序域承载 UI 的方法,其中也包含跨域(Cross-Domain)调用方法。
.NET Framework 自 3.5 以来推出了托管插件框架(MAF,Managed AddIn Framework),位于 System.AddIn
命名空间。其特性在于,将宿主和插件隔离在不同的应用程序域中,避免插件对宿主造成不良影响。
此命名空间中存在 FrameworkElementAdapters
类型,在 System.Windows.Presentation 程序集中,详见 。虽说主要用于 MAF 插件框架,但其实只需要此类型便可以实现跨应用程序域的 UI。
FrameworkElementAdapters
只有两个方法,ViewToContractAdapter
将 UI 转换成 INativeHandleContract
,而 ContractToViewAdapter
将 INativeHandleContract
用一个 FrameworkElement
进行承载。
public static class FrameworkElementAdapters{ [SecurityCritical] public static FrameworkElement ContractToViewAdapter(INativeHandleContract nativeHandleContract); [SecurityCritical] public static INativeHandleContract ViewToContractAdapter(FrameworkElement root);}
首先,我们需要有一个支持跨域调用的类型,并有任意的可以用来返回 INativeHandleContract
的方法。
internal sealed class DomainX : MarshalByRefObject{ public INativeHandleContract GetElement() { return FrameworkElementAdapters.ViewToContractAdapter( new Rectangle { Width = 200, Height = 100, Fill = Brushes.ForestGreen, }); }}
我们需要跨域创建这个 UI 控件并得到 INativeHandleContract
。
var domain = AppDomain.CreateDomain("X");var instance = (DomainX)domain.CreateInstanceAndUnwrap(typeof(DomainX).Assembly.FullName, typeof(DomainX).FullName);var contract = instance.GetElement();
然后,在需要承载这个跨域 UI 的地方取得这个 INativeHandleContract
的实例 contract
。
var element = FrameworkElementAsyncAdapters.ContractToViewAdapter(contract);// this 在这里是 MainWindow 或者 MainPage,或者其它任何能够承载 FrameworkElement 的对象。this.Content = element;
以上的这两端代码都可以写在 MainWindow
的 Loaded
事件中。
MAF 框架对插件和宿主程序集所在的文件夹结构有要求。这可是非常讨厌的一项特性!因为当我们希望采用 MAF 框架的时候,我们的应用程序可能已经有自己独特的一套目录了。就算我们从零开始写应用,采用 MAF 约定的方式组织 dll 也是很丑的方式(带有很重的 MAF 的影子)。
它没有提供任何的配置,而且如果不按照约定放置文件夹,还会发生如下错误: