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

我创建了一个具有不同基目录的AppDomain 。 但是,我似乎无法将当前正在执行的程序集加载到其他AppDomain中,而没有基本目录中当前正在执行的程序集的副本。 我甚至试图从字节加载它。

我尝试加载时没有exception,但是当我尝试使用时:

 domain.DoCallBack(new CrossAppDomainDelegate(... 

我明白了:

无法加载文件或程序集………..系统找不到指定的文件。

我的代码如下:

 private static void SaveAssemblies(Assembly ass, List assemblyByteList) { AssemblyName[] assNames = ass.GetReferencedAssemblies(); foreach (AssemblyName assName in assNames) { Assembly referedAss = Assembly.Load(assName); if (!referedAss.GlobalAssemblyCache) { SaveAssemblies(referedAss, assemblyByteList); } } byte[] rawAssembly = File.ReadAllBytes(ass.Location); assemblyByteList.Add(rawAssembly); } public static AppDomain CreateAppDomain(string dir, string name) { AppDomainSetup domainSetup = new AppDomainSetup(); domainSetup.ApplicationBase = dir; domainSetup.ApplicationName = Path.GetFileName(dir); domainSetup.PrivateBinPath = Path.Combine(dir, "Libs"); AppDomain domain = AppDomain.CreateDomain(name, null, domainSetup); //Load system assemblies needed for the module List assemblyByteList = new List(); SaveAssemblies(Assembly.GetExecutingAssembly(), assemblyByteList); foreach (byte[] rawAssembly in assemblyByteList) domain.Load(rawAssembly); domain.DoCallBack(new CrossAppDomainDelegate(SetupLogging)); return domain; } 

更新:

如果我查看输出,看起来组件已加载我看到了这一点

‘TaskExecuter.Terminal.vshost.exe’(托管(v4.0.30319)):已加载’NLog”TaskExecuter.Terminal.vshost.exe’(托管(v4.0.30319)):已加载’TaskExecuter’,已加载符号。

但我仍然得到例外……我不明白这一点

System.IO.FileNotFoundException未处理Message =无法加载文件或程序集’TaskExecuter,Version = 1.0.4244.31921,Culture = neutral,PublicKeyToken = null’或其依赖项之一。 该系统找不到指定的文件。 来源= mscorlib程序
FileName = TaskExecuter,Version = 1.0.4244.31921,Culture = neutral,PublicKeyToken = null FusionLog ====预绑定状态信息===日志:User = Peter-PC \ Peter LOG:DisplayName = TaskExecuter,Version = 1.0.4244.31921 ,Culture = neutral,PublicKeyToken = null(完全指定)日志:Appbase = file:/// C:/ ProgramData / TaskExecuter / TaskLib / uTorrentTasks日志:初始PrivatePath = C:\ ProgramData \ TaskExecuter \ TaskLib \ uTorrentTasks \ Libs调用汇编:(未知)。 ===日志:此绑定在默认加载上下文中启动。 日志:使用应用程序配置文件:d:\ users \ peter \ documents \ visual studio 2010 \ Projects \ TaskExecuter \ TaskExecuter.Terminal \ bin \ Release \ TaskExecuter.Terminal.vshost.exe.Config LOG:使用主机配置文件:LOG:使用C:\ Windows \ Microsoft.NET \ Framework \ v4.0.30319 \ config \ machine.config中的计算机配置文件。 日志:此时策略未应用于引用(私有,自定义,部分或基于位置的程序集绑定)。 日志:尝试下载新的URL文件:/// C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter.DLL。 日志:尝试下载新的URL文件:/// C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter/TaskExecuter.DLL。 日志:尝试下载新的URL文件:/// C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter.DLL。 日志:尝试下载新的URL文件:/// C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter/TaskExecuter.DLL。 日志:尝试下载新的URL文件:/// C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter.EXE。 日志:尝试下载新的URL文件:/// C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter/TaskExecuter.EXE。 日志:尝试下载新的URL文件:/// C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter.EXE。 日志:尝试下载新的URL文件:/// C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter/TaskExecuter.EXE。

StackTrace:System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName,String codeBase,System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName,String codeBase,Evidence assemblySecurity,RuntimeAssembly locationHint,StackCrawlMark&stackMark,Boolean throwOnFileNotFound,Boolean forIntrospection,Boolean suppressSecurityChecks))证据assemblySecurity,RuntimeAssembly locationHint,StackCrawlMark&stackMark,Boolean throwOnFileNotFound,Boolean forIntrospection,Boolean suppressSecurityChecks)System.Reflection.RuntimeAssembly.InternalLoad的System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef,Evidence assemblySecurity,StackCrawlMark&stackMark,Boolean forIntrospection,Boolean suppressSecurityChecks) (String assemblyString,Evidence assemblySecurity,StackCrawlMark&stackMark,Boolean forIntrospection)在System.Runtime.Serialization.FormatterServices.LoadAssemblyFromSt上的System.Reflection.Assembly.Load(String assemblyString)处。 位于System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)的System.Reflection.MemberInfoSerializationHolder..ctor(SerializationInfo info,StreamingContext context)中的ring(String assemblyName)位于D:\ users \中的TaskExecuter.AppDomainHelper.CreateAppDomain(String dir,String name) peter \ documents \ visual studio 2010 \ Projects \ TaskExecuter \ TaskExecuter \ AppDomainHelper.cs:在D:\ users \ peter \ documents \ visual studio 2010 \ Projects \ TaskExecuter \ TaskExecuter \的TaskExecuter.TaskManagment.TaskFinder.Probe()中的第50行TaskManagment \ TaskFinder.cs:位于TaskExecuter.TaskManagment的d:\ users \ peter \ documents \ visual studio 2010 \ Projects \ TaskExecuter \ TaskExecuter \ TaskManagment \ TaskManager.cs:第63行的TaskExecuter.TaskManagment.TaskManager.LoadTasks()第29行d:\ users \ peter \ documents \ visual studio 2010中的.TaskManager.Start()\ Projects \ TaskExecuter \ TaskExecuter \ TaskManagment \ TaskManager.cs:d中的TaskExecuter.Terminal.Program.Main(String [] args)中的第95行:\ users \ peter \ documents \ visual studio 2010 \ Proj ects \ TaskExecuter \ TaskExecuter.Terminal \ Program.cs:System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly,String [] args)第16行,位于Microsoft的System.AppDomain.ExecuteAssembly(String assemblyFile,Evidence assemblySecurity,String [] args)。 System.Threading.ExecutionContext.Run上System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback回调,对象状态,布尔值ignoreSyncCtx)的System.Threading.ThreadPel.ThreadStart_Context(对象状态)中的VisualStudio.HostingProcess.HostProc.RunUsersAssembly() System.Threading.ThreadHelper.ThreadStart()中的(ExecutionContext executionContext,ContextCallback回调,对象状态)
的InnerException:

我能够使用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”不同,我们遇到了一个看似非常清楚的exception。

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; 

这应该是正确的,我们是这么认为的,而且我们又错了,现在发生的事情是,实际上并没有实现初始化Application Domain并使用它,而是在我们连接事件处理程序以解析程序集的地方失败了。

同样,exception看起来非常像前面提到的,但是这次它找不到包含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)

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

因此,除非您的外部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,以便正确加载其他依赖项。

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

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

根据http://msdn.microsoft.com/en-us/library/aehss7y0.aspx,AppDomain.CreateDomain的行为已随.NET4更改,您应该使用http://msdn.microsoft.com/en-us/ library / ms130766.aspx和setup“手动” Evidencegrants ……

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

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

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

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

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

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

您可以尝试将AssemblyResolve事件处理程序注册到您的域中,但是使用它可以轻松地使用.dll地狱中的一些黑魔法变形。 如果其他一切都失败了你去.dll你自己,在这里见到我: 当DisallowApplicationBaseProbing = true时需要连接AssemblyResolve事件