在我的默认(完全信任)AppDomain中,我想创建一个沙箱AppDomain并订阅其中的一个事件:
class Domain : MarshalByRefObject
{
public event Action TestEvent;
}
Domain domain = AppDomainStarter.Start<Domain>(@"C:\Temp", "Domain", null, true);
domain.TestEvent += () => { }; // SecurityException
订阅失败,并显示消息“请求类型'System.Security.Permissions.ReflectionPermission,mscorlib,Version = 4.0.0.0 ...'的权限失败。”
(有关AppDomainStarter的定义,请参阅my answer to another question。)
请注意,ApplicationBase
C:\ Temp 是不包含包含域的程序集的文件夹。这是故意的;我的目标是在新的AppDomain中加载第二个第三方不受信任的程序集,第二个程序集位于C:\ Temp(或其他任何地方,甚至可能是网络共享)。但在我加载第二个程序集之前,我需要在新的AppDomain中加载Domain
类。这样做成功,但由于某种原因,我无法订阅跨越AppDomain边界的事件(我可以调用方法,但不能订阅事件)。
更新:显然,在订阅沙箱AppDomain中的事件时,订阅者方法和包含订阅者的类都必须是公共的。例如:
public static class Program
{
class Domain : MarshalByRefObject
{
public event Action TestEvent;
public Domain() { Console.WriteLine("Domain created OK"); }
}
static void Main()
{
string loc = @"C:\Temp";
Domain domain = AppDomainStarter.Start<Domain>(loc, "Domain", null, true);
// DIFFERENT EXCEPTION THIS TIME!
domain.TestEvent += new Action(domain_TestEvent);
}
public static void domain_TestEvent() { }
}
但是,我仍然无法订阅该活动。新错误是“无法加载文件或程序集'TestApp,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null'或其依赖项之一。系统找不到指定的文件。”
在某种程度上,这是有道理的,因为我将“错误”文件夹“C:\ Temp”指定为我的新AppDomain的ApplicationBase,但在某种程度上这没有任何意义,因为“TestApp”程序集是已在两个AppDomains中加载。 CLR怎么可能找不到已加载的程序集?
此外,如果我添加访问包含程序集的文件夹的权限,则没有区别:
string folderOfT = Path.GetFullPath(Path.Combine(typeof(T).Assembly.Location, ".."));
permSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, folderOfT));
// Same exception still occurs
我可以使用AppDomainSetup.ApplicationBase
的其他值“修复”问题:
string loc = Path.GetFullPath(Assembly.GetExecutingAssembly().Location + @"\..");
这消除了异常,但我无法使用此“解决方案”,因为AppDomain的目的是从包含我自己的程序集的文件夹以外的其他文件夹加载不受信任的程序集。因此,loc
必须是包含不受信任程序集的文件夹,而不是包含程序集的文件夹。
答案 0 :(得分:0)
例外来自部分信任域而不是完全信任域。必须省略才能在部分信任域中授予ReflectionPermission
答案 1 :(得分:0)
基于每个AppDomain解析程序集。由于您在另一个目录中启动新的AppDomain(并且您的程序集未在GAC中注册),因此无法找到程序集。您可以修改AppDomainStarter代码以首先加载目标程序集,然后从该程序集创建实例:
Assembly assembly = Assembly.LoadFrom(typeof(T).Assembly.ManifestModule.FullyQualifiedName);
return (T)assembly.CreateInstance(typeof(T).FullName, false, 0, null, constructorArgs, null, null);
答案 2 :(得分:0)
我能找到的唯一可行的方法是将程序集(包含您要在新AppDomain中运行的代码)放入GAC。当然,这对屁股来说是一个巨大的痛苦,但它是唯一可行的。
下面我将介绍一些我尝试过但不起作用的事情。
在某些情况下,Visual Studio 2010会在调用Activator.CreateInstanceFrom时给你这条消息(我不确定到底是什么时候 - 一个干净的控制台应用程序不会产生这个):
托管调试助手'LoadFromContext'检测到问题 在'C:\ Users ... \ TestApp.vshost.exe'中。附加信息:The 名为'TestApp'的程序集已加载 'file:/// C:/Users/.../TestApp.exe'使用LoadFrom上下文。使用 此上下文可能导致序列化的意外行为, 转换和依赖解析。几乎在所有情况下,它都是 建议避免使用LoadFrom上下文。这可以通过 在全局程序集缓存或中安装程序集 ApplicationBase目录并在显式时使用Assembly.Load 装载组件。
Assembly.LoadFrom的文档包括以下声明:“如果程序集加载了LoadFrom,并且稍后加载上下文中的程序集尝试按显示名称加载[相同的程序集],则加载尝试将失败。可以在程序集反序列化时发生。“可悲的是,没有暗示为什么会发生这种情况。
在示例代码中,程序集没有被反序列化(我不完全确定首先反序列化程序集的意义),但是一个委托正在被反序列化;假设反序列化委托涉及尝试“按显示名称”加载相同的程序集是合理的。
如果这是真的,如果委托指向位于使用“LoadFrom上下文”加载的程序集中的函数,则无法跨AppDomain边界传递委托。在这种情况下,使用CreateInstance
代替CreateInstanceFrom
可以避免此问题(因为CreateInstanceFrom
使用LoadFrom
):
return (T)Activator.CreateInstance(newDomain,
typeof(T).Assembly.FullName,
typeof(T).FullName, false,
0, null, constructorArgs, null, null).Unwrap();
但事实证明这是一个红鲱鱼;除非将CreateInstance
设置为包含程序集的文件夹,并且ApplicationBase
设置为该文件夹,否则无法使用ApplicationBase
,无论订阅TestEvent都会成功 >是否使用CreateInstance
或CreateInstanceFrom
在新AppDomain中创建T.因此,通过LoadFrom
加载T的事实并不会导致问题本身。
我尝试的另一件事是签署程序集并告诉.NET Framework它应该被赋予完全信任:
newDomain = AppDomain.CreateDomain(appDomainName, null, setup, permSet,
new StrongName[] { GetStrongName(typeof(T).Assembly) });
这取决于an MSDN article的GetStrongName方法。不幸的是,这没有任何效果(即仍然发生FileNotFoundException)。