.NET的程序集实际上不是直接在进程(Process)中运行,而是在一个特殊的上下文环境(AppDomain)中。我们的程序在运行的时候,首先会由CLR动态创建一个或多个默认的AppDomain,然后我们在代码中还可以手工地创建自定义的AppDomain。
有关程序域的说明,有兴趣的朋友可以参考下面的链接
http://msdn.microsoft.com/zh-cn/library/system.appdomain(VS.80).aspx
实际上它的三大好处是
1. 安全性(不同程序域之间默认是无法通讯的)
2. 稳定性(单一程序域的异常不会影响其他的)
3. 便捷性(可以利用程序域的单独卸载,而不是整个程序卸载)
那么什么时候需要用到这个技术呢?最典型的一个场景就是我们需要设计一个插件结构的程序。我们的主程序可以动态加载一个或者多个插件,这些插件当然可能是别人写的,那么我们对于这些插件所可能会做的事情是不可预期的。为了避免这些插件通过我们的主程序的执行而造成用户受到危害——例如插件可能会去更改注册表等等重要的信息——我们就可以把这些插件单独运行,而且让他们运行在相对比较低的安全上下文中。
下面用一个例子说明,我们大致的原理是用一个单独的应用程序域运行所有的插件,在该应用程序域上设置的权限是Internet这个级别。
核心代码如下
private AppDomain CreateAppDomain() { AppDomain result = AppDomain.CreateDomain("plugins"); PolicyLevel policy = PolicyLevel.CreateAppDomainLevel(); PermissionSet ps = policy.GetNamedPermissionSet("Internet");
//这里我们取得Internet这个权限集的默认权限集合 ps.AddPermission(new FileIOPermission(FileIOPermissionAccess.AllAccess, Application.StartupPath));
//并且再为其增加文件读取的权限,因为它需要加载另外的程序集,所以需要有主程序根目录下面的权限 policy.RootCodeGroup.PolicyStatement = new PolicyStatement(ps); result.SetAppDomainPolicy(policy); return result; }
下面的代码是菜单响应事件
void item_Click(object sender, EventArgs e) { ToolStripMenuItem item = (ToolStripMenuItem)sender; string tag = item.Tag.ToString(); string fileName = tag.Split(';')[0]; string typeName = tag.Split(';')[1]; BindingFlags bindings = BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public; AssemblyLoader loader = (AssemblyLoader) pluginDomain.CreateInstanceFromAndUnwrap("Host.exe", typeof(AssemblyLoader).FullName, true, bindings, null, new object[] { fileName }, null, null, null); loader.RunPlugin(typeName); }
上面的代码中的AssemblyLoader类相当于是一个桥梁,它帮助我们实例化插件并运行里面的入口方法
using System; using System.Reflection; using PluginSample.Core; namespace Host { [Serializable] public class AssemblyLoader:MarshalByRefObject { public AssemblyLoader() { } public AssemblyLoader(string dllName) { if (_assembly == null) _assembly = LoadAssembly(dllName); } private Assembly _assembly=null; public Assembly LoadAssembly(string fileName) { return Assembly.LoadFile(fileName); } public void RunPlugin(string type) { IPlugin plugin = (IPlugin)_assembly.CreateInstance(type); plugin.Main(); } } }
下面我们来看看运行的效果。首先说说主程序:因为主程序是在本机运行,所以默认使用到的权限集是FullTrust,如下图
我们这个程序会自动地加载plugins目录下面的dll,然后创建菜单。(你这里可以看见两个菜单项,分别对应两个插件)
我们接下去点击插件的菜单,这时候会调用上面贴出来的第二段代码,动态实例化插件,并将其放在单独的程序域中执行
因为我们给程序域设置的权限集是Internet,所以你会看到下面的效果
注意,我们这里为了测试,故意在这个插件上用一个按钮去修改注册表,我们来看看会出现什么情况
很好,这样我们就实现了目的:可以让插件运行,但不允许他去修改注册表。