2

在我的默认(完全信任)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 的定义,见我对另一个问题的回答。)

请注意,ApplicationBase C:\Temp不是包含包含域的程序集的文件夹。这是故意的;我的目标是在新的 AppDomain 中加载第二个第 3 方不受信任的程序集,第二个程序集位于 C:\Temp (或其他任何地方,甚至可能是网络共享)。但在我可以加载第二个程序集之前,我需要Domain在新的 AppDomain 中加载我的类。这样做会成功,但由于某种原因,我无法跨 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”程序集已经加载到两个 AppDomain 中. 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必须是包含不受信任程序集的文件夹,而不是包含我的程序集的文件夹。

4

3 回答 3

0

例外是来自部分信任域而不是完全信任域。您必须省略在部分信任域中授予 ReflectionPermission

于 2012-01-22T20:07:41.767 回答
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);
于 2012-03-06T07:17:02.293 回答
0

我能找到的唯一可行的方法是将程序集(包含要在新 AppDomain 中运行的代码)放入 GAC。当然,这是一个巨大的痛苦,但它是唯一有效的方法。

下面我将描述一些我尝试过但不起作用的事情。

在某些情况下,Visual Studio 2010 会在调用 Activator.CreateInstanceFrom 时为您提供此消息(我不确定确切的时间——干净的控制台应用程序不会产生此消息):

托管调试助手“LoadFromContext”在“C:\Users...\TestApp.vshost.exe”中检测到问题。附加信息:名为“TestApp”的程序集是使用 LoadFrom 上下文从“file:///C:/Users/.../TestApp.exe”加载的。使用此上下文可能会导致序列化、强制转换和依赖解析的意外行为。在几乎所有情况下,建议避免使用 LoadFrom 上下文。这可以通过在全局程序集缓存或 ApplicationBase 目录中安装程序集并在显式加载程序集时使用 Assembly.Load 来完成。

Assembly.LoadFrom的文档包括以下语句:“如果使用 LoadFrom 加载程序集,然后加载上下文中的程序集尝试按显示名称加载 [the] 相同的程序集,则加载尝试失败。当程序集已反序列化。” 可悲的是,没有任何迹象表明为什么会发生这种情况。

在示例代码中,Assembly没有被反序列化(而且我不完全确定反序列化 Assembly 意味着什么),但是一个委托正在被反序列化;可以合理地假设反序列化委托涉及尝试“按显示名称”加载相同的程序集。

如果这是真的,那么如果委托指向的函数位于使用“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否则无法使用,并且如果将 IS 设置为该文件夹,则订阅 TestEvent 将成功,无论是否或已用于在新的 AppDomain 中创建 T。因此,通过加载 T 的事实本身并不会导致问题。ApplicationBaseApplicationBaseCreateInstanceCreateInstanceFromLoadFrom

我尝试的另一件事是签署程序集并告诉 .NET 框架应该给予完全信任:

newDomain = AppDomain.CreateDomain(appDomainName, null, setup, permSet,
    new StrongName[] { GetStrongName(typeof(T).Assembly) });

这依赖于MSDN 文章中的 GetStrongName 方法。不幸的是,这没有任何效果(即 FileNotFoundException 仍然发生)。

于 2012-03-07T22:38:33.313 回答