.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,所以你会看到下面的效果
注意,我们这里为了测试,故意在这个插件上用一个按钮去修改注册表,我们来看看会出现什么情况
很好,这样我们就实现了目的:可以让插件运行,但不允许他去修改注册表。