2009-09-03 23 views
108

我的DLL由第三方應用程序加載,我們無法自定義。我的程序集必須位於它們自己的文件夾中。我無法將它們放入GAC(我的應用程序需要使用XCOPY進行部署)。 當根DLL嘗試從另一個DLL(在同一文件夾中)加載資源或類型時,加載失敗(FileNotFound)。 是否可以通過編程方式(從根DLL)將我的DLL所在的文件夾添加到程序集搜索路徑中?我不允許更改應用程序的配置文件。如何在運行時在.NET中將文件夾添加到程序集搜索路徑中?

回答

45

您可以將probing path添加到您的應用程序的.config文件,但只有探測路徑包含在您的應用程序的基本目錄中時才能使用。

+3

感謝您的補充。我見過很多次的'AssemblyResolve'解決方案,很好有另一個(和更容易)的選項。 – 2015-06-03 15:01:10

130

聽起來像你可以使用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; 
} 
+2

謝謝,Mattias! 這工作: AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + = new ResolveEventHandler(LoadFromSameFolderResolveEventHandler); static Assembly LoadFromSameFolderResolveEventHandler(object sender,ResolveEventArgs args) string folderPath = Path。GetDirectoryName(Assembly.GetExecutingAssembly()的位置。); string assemblyPath = Path.Combine(folderPath,args.Name +「.dll」);裝配組件= Assembly.LoadFrom(assemblyPath); return assembly; } – isobretatel 2009-09-03 16:03:52

+0

偉大的解決方案!這裏有一篇文章描述了其他一些解決方案,包括這一個詳細信息:http://kbalertz.com/897297/consume-assemblies-located-folder-different-application-folder-Visual-Basic.aspx(警告:它的VB ,而不是C#,有點舊) – Domenic 2011-12-21 20:51:41

+0

如果你想「後備」到基本的解析器,你會怎麼做。例如'if(!File.Exists(asmPath))return searchInGAC(...);' – 2017-01-29 22:25:02

3

直視AppDomain.AppendPrivatePath(不建議使用)或AppDomainSetup.PrivateBinPath

+10

From [MSDN](http://msdn.microsoft.com/en-us/library/system.appdomainsetup.aspx):Changing AppDomainSetup實例的屬性不會影響任何現有的AppDomain。當使用AppDomainSetup實例作爲參數調用CreateDomain方法時,它只會影響創建新的AppDomain。 – Nathan 2011-08-10 16:11:45

+1

['AppDomain.AppendPrivatePath'](https://msdn.microsoft.com/en-us/library/system.appdomain.appendprivatepath%28v=vs.110%29.aspx)的文檔似乎表明它應該支持動態擴展AppDomain的搜索路徑,只是該功能已被棄用。如果它工作,這是一個比重載'AssemblyResolve'更清潔的解決方案。 – binki 2015-04-08 06:01:20

8

最好的解釋from MS itself

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;   
} 
+0

'AssemblyResolve'用於CurrentDomain,不適用於另一個域'AppDomain.CreateDomain' – Kiquenet 2015-12-01 13:24:51

11

更新框架4

由於Framework 4提升了Assembl yResolve事件也適用於資源,實際上這個處理程序更好。它基於本地化在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; 
     } 

    } 
6

對於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); 
0

我'使用了@Mattias S'解決方案。如果您確實想要解析來自同一文件夾的依賴關係 - 則應嘗試使用請求程序集的位置,如下所示。 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; 
}; 
相關問題