如何在运行时在.NET中添加文件夹到程序集搜索路径?
我的DLL由第三方应用程序加载,我们无法自定义。 我的程序集必须位于它们自己的文件夹中。 我不能把它们放到GAC中(我的应用程序需要使用XCOPY进行部署)。 当根DLL尝试从另一个DLL(在同一个文件夹中)加载资源或类型时,加载失败(FileNotFound)。 是否有可能将我的DLL所在的文件夹以编程方式(从根DLL)添加到程序集搜索路径? 我不允许更改应用程序的配置文件。
听起来就像你可以使用AppDomain.AssemblyResolve事件,并手动加载DLL目录中的依赖关系。
编辑(从评论):
AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder); static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args) { string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll"); if (!File.Exists(assemblyPath)) return null; Assembly assembly = Assembly.LoadFrom(assemblyPath); return assembly; }
您可以将探测路径添加到应用程序的.config文件,但只有探测路径包含在您的应用程序的基本目录中时才能使用。
更新框架4
由于框架4也提高了AssemblyResolve事件的资源实际上这个处理程序效果更好。 它基于本地化在app子目录中的概念(一个用于文化名称的本地化,例如C:\ MyApp \ it意大利语)。里面有资源文件。 如果本地化是国家区域,即it-IT或pt-BR,则处理程序也起作用。 在这种情况下,处理程序“可能被多次调用:每个文件在回退链中一次”[来自MSDN]。 这意味着如果我们为“it-IT”资源文件返回null,则框架引发事件请求“it”。
事件挂钩
AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);
事件处理程序
Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { //This handler is called only when the common language runtime tries to bind to the assembly and fails. Assembly executingAssembly = Assembly.GetExecutingAssembly(); string applicationDirectory = Path.GetDirectoryName(executingAssembly.Location); string[] fields = args.Name.Split(','); string assemblyName = fields[0]; string assemblyCulture; if (fields.Length < 2) assemblyCulture = null; else assemblyCulture = fields[2].Substring(fields[2].IndexOf('=') + 1); string assemblyFileName = assemblyName + ".dll"; string assemblyPath; if (assemblyName.EndsWith(".resources")) { // Specific resources are located in app subdirectories string resourceDirectory = Path.Combine(applicationDirectory, assemblyCulture); assemblyPath = Path.Combine(resourceDirectory, assemblyFileName); } else { assemblyPath = Path.Combine(applicationDirectory, assemblyFileName); } if (File.Exists(assemblyPath)) { //Load the assembly from the specified path. Assembly loadingAssembly = Assembly.LoadFrom(assemblyPath); //Return the loaded assembly. return loadingAssembly; } else { return null; } }
MS本身的最佳解释:
AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler); private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args) { //This handler is called only when the common language runtime tries to bind to the assembly and fails. //Retrieve the list of referenced assemblies in an array of AssemblyName. Assembly MyAssembly, objExecutingAssembly; string strTempAssmbPath = ""; objExecutingAssembly = Assembly.GetExecutingAssembly(); AssemblyName[] arrReferencedAssmbNames = objExecutingAssembly.GetReferencedAssemblies(); //Loop through the array of referenced assembly names. foreach(AssemblyName strAssmbName in arrReferencedAssmbNames) { //Check for the assembly names that have raised the "AssemblyResolve" event. if(strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(","))) { //Build the path of the assembly from where it has to be loaded. strTempAssmbPath = "C:\\Myassemblies\\" + args.Name.Substring(0,args.Name.IndexOf(","))+".dll"; break; } } //Load the assembly from the specified path. MyAssembly = Assembly.LoadFrom(strTempAssmbPath); //Return the loaded assembly. return MyAssembly; }
对于C ++ / CLI用户,这里是@Mattias S的答案(适用于我):
using namespace System; using namespace System::IO; using namespace System::Reflection; static Assembly ^LoadFromSameFolder(Object ^sender, ResolveEventArgs ^args) { String ^folderPath = Path::GetDirectoryName(Assembly::GetExecutingAssembly()->Location); String ^assemblyPath = Path::Combine(folderPath, (gcnew AssemblyName(args->Name))->Name + ".dll"); if (File::Exists(assemblyPath) == false) return nullptr; Assembly ^assembly = Assembly::LoadFrom(assemblyPath); return assembly; } // put this somewhere you know it will run (early, when the DLL gets loaded) System::AppDomain ^currentDomain = AppDomain::CurrentDomain; currentDomain->AssemblyResolve += gcnew ResolveEventHandler(LoadFromSameFolder);
查看AppDomain.AppendPrivatePath(不建议使用)或AppDomainSetup.PrivateBinPath
我已经使用@Mattias S的解决方案。 如果你真的想解决来自同一个文件夹的依赖关系 – 你应该尝试使用Requesting assembly location,如下所示。 args.RequestingAssembly应该检查无效。
System.AppDomain.CurrentDomain.AssemblyResolve += (s, args) => { var loadedAssembly = System.AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName == args.Name).FirstOrDefault(); if(loadedAssembly != null) { return loadedAssembly; } if (args.RequestingAssembly == null) return null; string folderPath = Path.GetDirectoryName(args.RequestingAssembly.Location); string rawAssemblyPath = Path.Combine(folderPath, new System.Reflection.AssemblyName(args.Name).Name); string assemblyPath = rawAssemblyPath + ".dll"; if (!File.Exists(assemblyPath)) { assemblyPath = rawAssemblyPath + ".exe"; if (!File.Exists(assemblyPath)) return null; } var assembly = System.Reflection.Assembly.LoadFrom(assemblyPath); return assembly; };