Moje biblioteki DLL są ładowane przez aplikację innej firmy, której nie możemy dostosować. Moje zestawy muszą znajdować się we własnym folderze. Nie mogę umieścić ich w GAC (moja aplikacja wymaga wdrożenia przy użyciu XCOPY). Gdy główna biblioteka DLL próbuje załadować zasób lub typ z innej biblioteki DLL (w tym samym folderze), ładowanie kończy się niepowodzeniem (FileNotFound). Czy możliwe jest programowe dodanie folderu, w którym znajdują się moje biblioteki DLL, do ścieżki wyszukiwania zestawu (z głównej biblioteki DLL)? Nie wolno mi zmieniać plików konfiguracyjnych aplikacji.
.net
search
assemblies
path
isobretatel
źródło
źródło
if (!File.Exists(asmPath)) return searchInGAC(...);
Możesz dodać ścieżkę sondowania do pliku .config aplikacji, ale będzie ona działać tylko wtedy, gdy ścieżka sondowania znajduje się w katalogu podstawowym aplikacji.
źródło
AssemblyResolve
Wiele razy widziałem to rozwiązanie, dobrze jest mieć inną (i łatwiejszą) opcję.Aktualizacja dla Framework 4
Ponieważ Framework 4 podnosi zdarzenie AssemblyResolve również dla zasobów, w rzeczywistości ta procedura obsługi działa lepiej. Opiera się na założeniu, że lokalizacje znajdują się w podkatalogach aplikacji (jeden do lokalizacji z nazwą kultury, np. C: \ MyApp \ it dla włoskiego). Wewnątrz znajduje się plik zasobów. Program obsługi działa również, jeśli lokalizacja to kraj-region, tj. It-IT lub pt-BR. W tym przypadku procedura obsługi „może zostać wywołana wiele razy: raz dla każdej kultury w łańcuchu awaryjnym” [z MSDN]. Oznacza to, że jeśli zwrócimy wartość null dla pliku zasobów „it-IT”, framework zgłasza zdarzenie z prośbą o „it”.
Hak zdarzenia
AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);
Procedura obsługi zdarzeń
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; } }
źródło
AssemblyName
konstruktora, aby zdekodować nazwę zestawu zamiast polegać na analizowaniu ciągu zestawu.Najlepsze wyjaśnienie od samego 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; }
źródło
AssemblyResolve
dotyczy domeny CurrentDomain, nie dotyczy innej domenyAppDomain.CreateDomain
Dla użytkowników C ++ / CLI, oto odpowiedź @Mattias S (która działa dla mnie):
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);
źródło
Użyłem rozwiązania @Mattias S. Jeśli faktycznie chcesz rozwiązać zależności z tego samego folderu - powinieneś spróbować użyć Żądania lokalizacji zestawu , jak pokazano poniżej. args.RequestingAssembly należy sprawdzić pod kątem nieważności.
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; };
źródło
zajrzyj do AppDomain.AppendPrivatePath (przestarzałe) lub AppDomainSetup.PrivateBinPath
źródło
AppDomain.AppendPrivatePath
Dokumentacja wydaje się sugerować, że powinna obsługiwać dynamiczne rozszerzanieAppDomain
ścieżki przeszukiwania, tylko że ta funkcja jest przestarzała. Jeśli to zadziała, jest to znacznie czystsze rozwiązanie niż przeciążenieAssemblyResolve
.AppDomain.AppendPrivatePath
nic nie robi w .NET Core i aktualizuje.PrivateBinPath
w pełnej strukturze .Przyszedłem tutaj z innego (oznaczonego jako duplikat) pytania o dodanie tagu sondującego do pliku App.Config.
Chcę dodać do tego notatkę boczną - program Visual studio wygenerował już plik App.config, jednak dodanie tagu sondującego do wstępnie wygenerowanego tagu runtime nie zadziałało! potrzebujesz osobnego tagu runtime z dołączonym tagiem sondującym. W skrócie, Twój App.Config powinien wyglądać tak:
<?xml version="1.0" encoding="utf-8"?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Text.Encoding.CodePages" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" /> </dependentAssembly> </assemblyBinding> </runtime> <!-- Discover assemblies in /lib --> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="lib" /> </assemblyBinding> </runtime> </configuration>
Zrozumienie tego zajęło trochę czasu, więc zamieszczam to tutaj. Również kredyty dla pakietu NuGet PrettyBin . Jest to pakiet, który automatycznie przenosi biblioteki DLL. Podobało mi się bardziej ręczne podejście, więc nie korzystałem z niego.
Ponadto - tutaj jest skrypt po kompilacji, który kopiuje wszystkie pliki .dll / .xml / .pdb do / Lib. To porządkuje folder / debug (lub / release), co moim zdaniem ludzie próbują osiągnąć.
:: Moves files to a subdirectory, to unclutter the application folder :: Note that the new subdirectory should be probed so the dlls can be found. SET path=$(TargetDir)\lib if not exist "%path%" mkdir "%path%" del /S /Q "%path%" move /Y $(TargetDir)*.dll "%path%" move /Y $(TargetDir)*.xml "%path%" move /Y $(TargetDir)*.pdb "%path%"
źródło