.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
감사합니다, Mattias! 작동합니다 : AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + = 새로운 ResolveEventHandler (LoadFromSameFolderResolveEventHandler); 정적 어셈블리 LoadFromSameFolderResolveEventHandler (객체 전송자, ResolveEventArgs 인수) {문자열 folderPath = Path.GetDirectoryName (Assembly.GetExecutingAssembly (). Location); 문자열 assemblyPath = Path.Combine (folderPath, args.Name + ".dll"); 어셈블리 어셈블리 = Assembly.LoadFrom (assemblyPath); 반품 어셈블리; }
isobretatel

1
기본 리졸버로 "대체"하려면 어떻게해야합니까? 예if (!File.Exists(asmPath)) return searchInGAC(...);
Tomer W

57

프로빙 경로 를 응용 프로그램의 .config 파일에 추가 할 수 있지만 프로빙 경로가 응용 프로그램의 기본 디렉토리에 포함 된 경우에만 작동합니다.


3
이것을 추가해 주셔서 감사합니다. AssemblyResolve솔루션을 여러 번 보았 으므로 다른 (더 쉬운) 옵션을 사용하는 것이 좋습니다.
사무엘 네프

1
앱을 다른 곳에 복사하는 경우 App.config 파일을 앱과 함께 옮기는 것을 잊지 마십시오.
Maxter

12

프레임 워크 4 업데이트

프레임 워크 4는 자원에 대해서도 AssemblyResolve 이벤트를 발생시키기 때문에 실제로이 핸들러는 더 잘 작동합니다. 현지화가 앱 하위 디렉토리에 있다는 개념을 기반으로합니다 (하나는 문화의 이름을 가진 현지화 (예 : 이탈리아어의 경우 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;
        }

    }

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는 현재 도메인 용이며 다른 도메인에는 유효하지 않습니다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.RequestingAssemblynull인지 확인해야합니다.

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 인스턴스의 속성을 변경하면 기존의 응용 프로그램 도메인에 영향을주지 않습니다. AppDomainSetup 인스턴스를 매개 변수로 사용하여 CreateDomain 메서드를 호출하면 새 AppDomain 생성에만 영향을 줄 수 있습니다.
Nathan

2
AppDomain.AppendPrivatePath님의 문서는 AppDomain의 기능이 더 이상 사용되지 않으므로 검색 경로를 동적으로 확장하도록 지원해야한다고 제안하는 것 같습니다 . 작동하면 과부하보다 훨씬 깨끗한 솔루션 AssemblyResolve입니다.
binki


3

Probing 태그를 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>

알아내는 데 시간이 걸렸으므로 여기에 게시하고 있습니다. 또한 PrettyBin NuGet 패키지의 크레딧 입니다. dll을 자동으로 이동시키는 패키지입니다. 더 수동적 인 접근 방식을 좋아해서 사용하지 않았습니다.

또한 모든 .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%"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.