将当前程序集加载到不同的AppDomain中

时间:2011-07-31 23:54:14

标签: c# .net appdomain

6 个答案:

答案 0 :(得分:8)

我能够使用archive.org恢复链接到博客文章,并提出了一个可行的解决方案。

我的目标是将exe动态编译到临时位置,然后让exe阴影加载子appdomain中的所有主dll,以便可以轻松更新生成exe的主应用程序。基本方法是使用childAppDomain.CreateInstanceFrom创建一个在构造函数中安装程序集解析事件处理程序的类型。我的代码看起来像

var exportAppDomain = AppDomain.CreateDomain(
    runnerName,
    null,
    appDomainSetup,
    new PermissionSet(PermissionState.Unrestricted));

exportAppDomain.CreateInstanceFrom(
    Assembly.GetExecutingAssembly().Location,
    "ExportLauncher.AppDomainResolver",
    true,
    BindingFlags.Public | BindingFlags.Instance,
    null,
    new object[] { Assembly.GetExecutingAssembly().Location },
    null,
    null);

创建所需AssemblyResolve处理程序的类型(下面的博客文章描述了为什么需要其他类型)

class AppDomainResolver
{
    string _sourceExeLocation;

    public AppDomainResolver(string sourceExeLocation)
    {
        _sourceExeLocation = sourceExeLocation;
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    }

    Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        if (args.Name.Contains("exporterLauncher")) // why does it not already know it has this assembly loaded? the seems to be required
            return typeof(AppDomainResolver).Assembly;
        else
            return null;
    }
}

以下是原始博文:

应用域很难......

你有没有在.NET中使用Application Domain?一开始它看起来并不那么困难,但是你了解它们就会开始意识到所有的困难。

只要您不移动到Host AppDomains.BaseDirectory之外,一切正常,但在我们的情况下,我们希望在主机应用程序的位置“C:\ My Plug-ins”部署插件运行在“C:\ Program Files \ My App”,因为我们可能会遇到从AppDomain到某些主机程序集的依赖关系,这显然是不可避免的。

经典 这是一些简单的代码和我们的第一次尝试。

 1:  string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
   2:  AppDomainSetup setup = new AppDomainSetup
   3:  {
   4:      ApplicationName = name,
   5:      ApplicationBase = applicationBase,
   6:      PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
   7:      PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
   8:      ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
   9:  };
  10:   
  11:  Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
  12:  AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);

看起来非常简单,但由于“ApplicationBase”与“AppDomain.CurrentDomain.BaseDirectory”不同,我们遇到了一个看似非常清楚的异常。

System.IO.FileNotFoundException:无法加载文件或程序集' Host.Services,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null'或其中一个依赖项。系统找不到指定的文件。

如果您使用过任何类型的动态加载程序集,我相信您很熟悉。问题是“Host.Services”在Host Application Domain中是已知的,因为它存储在“C:\ Program Files \ My App”中,而寻找它的Application Domain正在查看“C:\ My Plug-插件”。

我们以为我们已经指示它也会查看“AppDomain.CurrentDomain.BaseDirectory”,它将是“C:\ Program Files \ My App”,但事实并非如此。

AppDomain.AssemblyResolve救援? 好的,所以我们之前一直在使用这些怪癖,所以我们知道如何使用“AppDomain.AssemblyResolve”手动解析它自己无法处理的AppDomain的任何程序集。

1:  string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
   2:  AppDomainSetup setup = new AppDomainSetup
   3:  {
   4:      ApplicationName = name,
   5:      ApplicationBase = applicationBase,
   6:      PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
   7:      PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
   8:      ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
   9:  };
  10:   
  11:  Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
  12:  AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);
  13:  domain.AssemblyResolve += Resolve;

这应该是正常的,我们是这么认为的,我们又错了,现在发生的事情是,而不是实际上初始化应用程序域并使用它,而是在我们连接事件处理程序的地方失败用于解决组件。

同样异常看起来非常像前面提到的那样,但是这次它找不到包含Type的程序集,它包含我们在上面代码片段最后一行中设置的“Resolve”处理程序。

AppDomain.Load然后! 好吧,很明显在连接事件处理程序时,Application Domain需要知道处理该事件的对象的类型,当你考虑它时实际上是可以理解的,所以如果Application Domain甚至找不到那个,加载我们无法真正处理任何事情。

接下来是什么?我们的想法是手动指示Application Domain加载一个浅组件,该组件没有任何其他依赖关系,可以在GAC中找到,并挂钩事件处理程序。

 1:  string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
   2:  AppDomainSetup setup = new AppDomainSetup
   3:  {
   4:      ApplicationName = name,
   5:      ApplicationBase = applicationBase,
   6:      PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
   7:      PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
   8:      ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
   9:  };
  10:   
  11:  Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
  12:  AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);
  13:  domain.Load(File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Host.AssemblyLoader.dll")));
  14:  domain.AssemblyResolve += new AssemblyLoader(AppDomain.CurrentDomain.BaseDirectory).Handle;

使用如下所示的非常简单的小类,并且不介意奇怪的Resolve行为。

 1:  [Serializable]
   2:  public class AssemblyLoader
   3:  {
   4:      private string ApplicationBase { get; set; }
   5:   
   6:      public AssemblyLoader(string applicationBase)
   7:      {
   8:          ApplicationBase = applicationBase;
   9:      }
  10:   
  11:      public Assembly Resolve(object sender, ResolveEventArgs args)
  12:      {
  13:          AssemblyName assemblyName = new AssemblyName(args.Name);
  14:          string fileName = string.Format("{0}.dll", assemblyName.Name);
  15:          return Assembly.LoadFile(Path.Combine(ApplicationBase, fileName));
  16:      }
  17:  }

所以是或否?......不!......同样的问题仍然存在。

事情要简单得多! 事实上,当我们设法让它发挥作用时,事情变得更加简单。

我不能说.NET团队究竟如何设想这应该有效,我们无法找到“PrivateBinPath”和“PrivateBinPathProbe”用于的任何可用的东西。好吧,我们现在就使用它们,让它们像我们预期的那样工作!

所以我们改为将“AssemblyLoader”类改为:

   1:  [Serializable]
   2:  public class AssemblyLoader : MarshalByRefObject
   3:  {
   4:      private string ApplicationBase { get; set; }
   5:   
   6:      public AssemblyLoader()
   7:      {
   8:          ApplicationBase = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
   9:          AppDomain.CurrentDomain.AssemblyResolve += Resolve;
  10:      }
  11:   
  12:      private Assembly Resolve(object sender, ResolveEventArgs args)
  13:      {
  14:          AssemblyName assemblyName = new AssemblyName(args.Name);
  15:          string fileName = string.Format("{0}.dll", assemblyName.Name);
  16:          return Assembly.LoadFile(Path.Combine(ApplicationBase, fileName));
  17:      }
  18:  }

因此,我们不是将我们创建应用程序域的事件挂钩,而是让它自己完成它,而不是“CurrentDomain”。

好的等等,这不会导致在工厂中创建它时出现问题,因为它现在正在加载错误的域名吗?谢天谢地,您可以从外部在域内创建对象。

因此,现在按如下方式创建域:

1:  string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
   2:  AppDomainSetup setup = new AppDomainSetup
   3:  {
   4:      ApplicationName = name,
   5:      ApplicationBase = applicationBase,
   6:      PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
   7:      PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
   8:      ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
   9:  };
  10:   
  11:  Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
  12:  AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);
  13:  domain.CreateInstanceFrom(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Host.AssemblyLoader.dll"),"Host.AssemblyLoader");

我们甚至不关心维护对“AssemblyLoader”的引用,因为它应该通过将它自己挂钩到事件来保持活着。

希望这可以帮助那些偶然发现同样问题的人,我看到很多解决方法,然后人们就让插件安装在同一个主机目录中,尽管已经将所有必要的依赖项与插件一起部署了它不是插件知道它依赖的东西等等。

以上至少使我们能够从我们认为不错的主机应用程序库中安装插件。

如果有人以不同方式解决了这个问题,那么请回复,也许我们可以找到任何方式的利弊,或者只是找到更好的解决方案。

如果您有任何疑问或无法完成上述工作,请随时提出。

作者:Jens Melgaard |发表于@ Thursday,July 01,2010 3:08 PM |反馈(0)

答案 1 :(得分:3)

你有没有理由不使用原始组件?

因此,除非您的外部appdomain使用阻止其访问原始程序集的凭据,否则AppDomain.CreateInstanceFromAndUnwrap方法能够执行此操作。

我建议您使用类似这样的类在MarshalByRefObject类中隔离远程执行的代码:

public class MyRemoteClass : MarshalByRefObject
{
    public void SetupLogging()
    { 
       // ...
    }
}

并像这样使用它:

var assemblyPath = new Uri(typeof(MyRemoteClass).Assembly.CodeBase).LocalPath;
var remote = (MyRemoteClass)domain.CreateInstanceFromAndUnwrap(assemblyPath, "NameSpace.MyRemoteClass");

remote.SetupLogging();

这将避免通过appdomain状态传递返回值的不必要麻烦,因为DoCallBack不返回值。这也可以避免将AppDomain管道代码与您的应用程序逻辑混合。

最后,您可能需要拦截MyRemoteClass中的AppDomain.AssemblyResolve,以便正确加载其他依赖项。

答案 2 :(得分:2)

从字节设置加载程序集后找到一个解决方案.GetName()。CodeBase为null解决了问题......

环顾四周后我找到了this页面,它有一个比我更好的解决方案!

答案 3 :(得分:1)

根据http://msdn.microsoft.com/en-us/library/aehss7y0.aspxAppDomain.CreateDomain的行为已随.NET4发生变化,您应该使用http://msdn.microsoft.com/en-us/library/ms130766.aspx并设置Evidencegrants“手动”。

答案 4 :(得分:0)

如果您需要自己加载程序集,请避免从字节加载...我建议至少使用完整程序集加载。

一般来说,要研究为“fusion log viewer”(http://www.bing.com/search?q=fussion+log+viewer)加载程序集serach的问题,并使用该工具查看代码尝试从哪里加载程序集。

答案 5 :(得分:0)

我有根据的猜测是你错过了错误信息的一个重要部分:

System.IO.FileNotFoundException未处理Message =无法加载文件或 程序集'TaskExecuter,Version = 1.0.4244.31921,Culture = neutral,PublicKeyToken = null' 或其中一个依赖项。该系统找不到指定的文件。源= mscorlib程序

除了无法从ApplicationBase下的其他位置加载程序集之外,可能还有一些dependend程序集从可以解析和加载的地方丢失。

顺便说一句,如果你从字节开始加载,你应该看看加载到你的域的程序集。可能已加载dependend程序集,但无法自动解析依赖项。如果两次加载相同的程序集,则其类型将不兼容。你会得到一些有趣的CastExceptions,说无法将YourClass的对象强制转换为YourClass。

您可以尝试将AssemblyResolve事件处理程序注册到您的域中,但是使用它可以很容易地使用.dll地狱中的一些黑魔法变形。 如果其他一切都失败了你就去了.dll你自己去找我,在这里见到我: Need to hookup AssemblyResolve event when DisallowApplicationBaseProbing = true