Як додати папку до шляху пошуку збірки під час виконання в .NET?


130

Мої DLL завантажуються стороннім додатком, який ми не можемо налаштувати. Мої збори повинні бути розміщені у власній папці. Я не можу помістити їх у GAC (у моїй програмі є вимога бути розгорнутим за допомогою XCOPY). Коли коренева DLL намагається завантажити ресурс або тип з іншої DLL (у тій же папці), завантаження не вдалося (FileNotFound). Чи можливо додати папку, де розміщені мої DLL, до шляху пошуку збірки програмно (з кореневої DLL)? Мені заборонено змінювати файли конфігурації програми.

Відповіді:


154

Здається, ви можете використовувати подію 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;
}

4
Дякую, Маттіас! Це працює: AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + = новий ResolveEventHandler (LoadFromSameFolderResolveEventHandler); статична збірка LoadFromSameFolderResolveEventHandler (відправник об'єкта, аргументи ResolveEventArgs) {string folderPath = Path.GetDirectoryName (Assembly.GetExecutingAssembly (). Розташування); string AssemblyPath = Path.Combine (папкаPath, args.Name + ".dll"); Асемблерна збірка = Assembly.LoadFrom (AssemblyPath); зворотний вузол; }
isobretatel

1
Що б ви зробили, якби хотіли "повернутися" до базового Resolver. напр.if (!File.Exists(asmPath)) return searchInGAC(...);
Tomer W

57

Ви можете додати шлях зондування до файлу .config програми, але він працюватиме лише у тому випадку, якщо шлях зондування міститься в базовому каталозі вашої програми.


3
Дякуємо, що додали це. Я бачив AssemblyResolveрішення стільки разів, добре мати ще один (і простіший) варіант.
Самуель Нефф

1
Не забудьте перенести файл App.config у свою програму, якщо ви скопіюєте його деінде ..
Maxter

12

Оновлення для Framework 4

Оскільки Framework 4 піднімає подію AssemblyResolve також для ресурсів, цей обробник працює краще. Він заснований на концепції, що локалізації знаходяться у підкаталогах додатків (одна для локалізації з назвою культури, тобто C: \ MyApp \ it для італійської). Всередині знаходиться файл ресурсів. Обробник також працює, якщо локалізація є країною-регіоном, тобто IT-IT або pt-BR. У цьому випадку обробник "може бути викликаний кілька разів: один раз для кожної культури в запасному ланцюзі" [від MSDN]. Це означає, що якщо ми повернемо null для файлу ресурсів "it-IT", рамка викликає подію із запитом "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;
        }

    }

Ви можете використовувати AssemblyNameконструктор для декодування імені збірки замість того, щоб покладатися на розбір рядка збірки.
Sebazzz

10

Найкраще пояснення від самої 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;          
}

AssemblyResolveпризначений для CurrentDomain, не дійсний для іншого доменуAppDomain.CreateDomain
Kiquenet

8

Для користувачів 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);

6

Я використовував рішення @Mattias S '. Якщо ви насправді хочете вирішити залежності від тієї ж папки - спробуйте скористатися Запити запиту місця складання , як показано нижче. args.RequestingAssemble слід перевірити на предмет недійсності.

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

4

перегляньте AppDomain.AppendPrivatePath (застарілий) або AppDomainSetup.PrivateBinPath


11
Від MSDN : Зміна властивостей екземпляра AppDomainSetup не впливає на будь-який існуючий AppDomain. Це може вплинути лише на створення нового AppDomain, коли метод CreateDomain викликається з екземпляром AppDomainSetup як параметр.
Натан

2
AppDomain.AppendPrivatePathЗдається, що документація підказує, що вона повинна підтримувати динамічно розширюється AppDomainшлях пошуку, лише те, що функція застаріла. Якщо це працює, це набагато більш чисте рішення, ніж перевантаження AssemblyResolve.
бінкі


3

Я прийшов сюди з іншого (позначеного дубліката) питання щодо додавання тегу зондування до файлу App.Config.

Я хочу додати до цього сторонне позначення - Visual studio вже створив файл App.config, однак додавання тегу зондування до попередньо створеного тегу виконання не працювало! вам потрібен окремий тег виконання з включеним тегом зондування. Коротше кажучи, ваш App.Config повинен виглядати так:

<?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>

Це зайняло деякий час, щоб розібратися, тому я публікую його тут. Також зараховує пакет The PrettyBin NuGet . Це пакет, який автоматично переміщує dlls. Мені сподобався більш ручний підхід, тому я його не використовував.

Також - ось сценарій створення збірки, який копіює всі .dll / .xml / .pdb в / Lib. Це скасовує папку / debug (або / release), чого я думаю, що люди намагаються досягти.

:: 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%"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.