다음을 통해 공유


어셈블리 로드 해결

.NET에서는 어셈블리 로드를 보다 효율적으로 제어해야 하는 애플리케이션에 대해 AppDomain.AssemblyResolve 이벤트를 제공합니다. 이 이벤트를 처리하면 애플리케이션이 정상적인 검색 경로 외부에서 로드 컨텍스트에 어셈블리를 로드하고, 여러 어셈블리 버전 중에서 로드할 버전을 선택하고, 동적 어셈블리를 내보내 반환하는 작업 등을 수행할 수 있습니다. 이 항목에서는 AssemblyResolve 이벤트 처리 지침을 제공합니다.

참고 항목

리플렉션 전용 컨텍스트에서 어셈블리 로드를 확인하려면 AppDomain.ReflectionOnlyAssemblyResolve 이벤트를 대신 사용합니다.

AssemblyResolve 이벤트 작동 방식

AssemblyResolve 이벤트 처리기를 등록하면 런타임에 이름으로 어셈블리에 바인딩하지 못할 때마다 처리기가 호출됩니다. 예를 들어 사용자 코드에서 다음 메서드를 호출하면 AssemblyResolve 이벤트가 발생할 수 있습니다.

이벤트 처리기가 수행하는 작업

AssemblyResolve 이벤트 처리기는 ResolveEventArgs.Name 속성을 통해 로드할 어셈블리의 표시 이름을 받습니다. 처리기가 어셈블리 이름을 인식하지 못하는 경우 null(C#), Nothing(Visual Basic) 또는 nullptr(Visual C++)이 반환됩니다.

처리기가 어셈블리 이름을 인식하면 요청을 충족하는 어셈블리를 로드하고 반환할 수 있습니다. 다음 목록에서는 몇 가지 샘플 시나리오를 설명합니다.

  • 처리기가 어셈블리 버전의 위치를 알고 있다면 Assembly.LoadFrom 또는 Assembly.LoadFile 메서드를 사용하여 어셈블리를 로드할 수 있으며, 성공할 경우 로드된 어셈블리를 반환할 수 있습니다.

  • 처리기가 바이트 배열로 저장된 어셈블리 데이터베이스에 액세스할 수 있는 경우 바이트 배열을 사용하는 Assembly.Load 메서드 오버로드 중 하나를 사용하여 바이트 배열을 로드할 수 있습니다.

  • 처리기는 동적 어셈블리를 생성하고 반환할 수 있습니다.

참고 항목

처리기는 어셈블리를 로드-원본 컨텍스트, 로드 컨텍스트 또는 컨텍스트 없이 로드해야 합니다. 처리기가 Assembly.ReflectionOnlyLoad 또는 Assembly.ReflectionOnlyLoadFrom 메서드를 사용하여 어셈블리를 리플렉션 전용 컨텍스트로 로드하는 경우 AssemblyResolve 이벤트를 발생시킨 로드 시도가 실패합니다.

적합한 어셈블리를 반환하는 것은 이벤트 처리기의 책임입니다. 처리기는 ResolveEventArgs.Name 속성 값을 AssemblyName(String) 생성자에 전달하여 요청된 어셈블리의 표시 이름을 구문 분석할 수 있습니다. .NET Framework 4부터 처리기는 ResolveEventArgs.RequestingAssembly 속성을 사용하여 현재 요청이 다른 어셈블리의 종속성인지 여부를 확인합니다. 이 정보는 종속성을 충족하는 어셈블리를 식별하는 데 도움이 됩니다.

이벤트 처리기는 요청된 버전과 다른 어셈블리 버전을 반환할 수 있습니다.

대부분의 경우 처리기에서 반환되는 어셈블리는 처리기가 로드하는 컨텍스트에 관계없이 로드 컨텍스트에 나타납니다. 예를 들어 처리기가 Assembly.LoadFrom 메서드를 사용하여 어셈블리를 로드 소스 컨텍스트에 로드하는 경우 처리기에서 반환되는 어셈블리는 로드 컨텍스트에 표시됩니다. 그러나 다음과 같은 경우에는 처리기에서 반환되는 어셈블리가 컨텍스트 없이 나타납니다.

컨텍스트에 대한 자세한 내용은 Assembly.LoadFrom(String) 메서드 오버로드를 참조하세요.

동일한 어셈블리의 여러 버전을 동일한 애플리케이션 도메인에 로드할 수 있습니다. 이 방법은 형식 할당 문제를 일으킬 수 있으므로 권장되지 않습니다. 최선의 어셈블리 로드 방법을 참조하세요.

이벤트 처리기에서 수행하지 않아야 하는 작업

AssemblyResolve 이벤트 처리의 기본 규칙은 알 수 없는 어셈블리를 반환하지 않아야 한다는 것입니다. 처리기를 작성할 때 이벤트를 발생시킬 수 있는 어셈블리를 알아야 합니다. 처리기는 다른 어셈블리에 대해 null을 반환해야 합니다.

Important

.NET Framework 4부터 위성 어셈블리에 대해 AssemblyResolve 이벤트가 발생합니다. 이 변경 내용은 처리기가 모든 어셈블리 로드 요청을 확인하려는 경우 이전 버전의 .NET Framework에 대해 작성된 이벤트 처리기에 영향을 줍니다. 알 수 없는 어셈블리를 무시하는 이벤트 처리기는 이러한 변경의 영향을 받지 않습니다. null을 반환하고 일반적인 대체 메커니즘을 따릅니다.

어셈블리를 로드할 때 이벤트 처리기는 AssemblyResolve 이벤트가 재귀적으로 발생될 수 있게 하여 스택 오버플로가 발생할 수 있는 AppDomain.Load 또는 Assembly.Load 메서드 오버로드를 사용하면 안 됩니다. 이 항목의 앞부분에 제공된 목록을 참조하세요. 이 문제는 모든 이벤트 처리기가 반환될 때까지 예외가 throw되지 않기 때문에 로드 요청에 대한 예외 처리를 제공하는 경우에도 발생합니다. 따라서 다음 코드는 MyAssembly를 찾을 수 없는 경우 스택 오버플로를 발생시킬 수 있습니다.

using System;
using System.Reflection;

class BadExample
{
    static void Main()
    {
        AppDomain ad = AppDomain.CreateDomain("Test");
        ad.AssemblyResolve += MyHandler;

        try
        {
            object obj = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    static Assembly MyHandler(object source, ResolveEventArgs e)
    {
        Console.WriteLine("Resolving {0}", e.Name);
        // DO NOT DO THIS: This causes a StackOverflowException
        return Assembly.Load(e.Name);
    }
}

/* This example produces output similar to the following:

Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
...
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null

Process is terminated due to StackOverflowException.
 */
Imports System.Reflection

Class BadExample

    Shared Sub Main()

        Dim ad As AppDomain = AppDomain.CreateDomain("Test")
        AddHandler ad.AssemblyResolve, AddressOf MyHandler

        Try
            Dim obj As object = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType")
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub

    Shared Function MyHandler(ByVal source As Object, _
                              ByVal e As ResolveEventArgs) As Assembly
        Console.WriteLine("Resolving {0}", e.Name)
        // DO NOT DO THIS: This causes a StackOverflowException
        Return Assembly.Load(e.Name)
    End Function
End Class

' This example produces output similar to the following:
'
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'...
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'
'Process is terminated due to StackOverflowException.
using namespace System;
using namespace System::Reflection;

ref class Example
{
internal:
    static Assembly^ MyHandler(Object^ source, ResolveEventArgs^ e)
    {
        Console::WriteLine("Resolving {0}", e->Name);
        // DO NOT DO THIS: This causes a StackOverflowException
        return Assembly::Load(e->Name);
    }
};

void main()
{
    AppDomain^ ad = AppDomain::CreateDomain("Test");
    ad->AssemblyResolve += gcnew ResolveEventHandler(&Example::MyHandler);

    try
    {
        Object^ obj = ad->CreateInstanceAndUnwrap(
            "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
            "MyType");
    }
    catch (Exception^ ex)
    {
        Console::WriteLine(ex->Message);
    }
}

/* This example produces output similar to the following:

Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
...
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null

Process is terminated due to StackOverflowException.
*/

AssemblyResolve를 처리하는 올바른 방법

AssemblyResolve 이벤트 처리기에서 어셈블리를 확인할 때 처리기가 Assembly.Load 또는 AppDomain.Load 메서드 호출을 사용하면 결국 StackOverflowException이 throw됩니다. 대신 AssemblyResolve 이벤트를 발생시키지 않는 LoadFile 또는 LoadFrom 메서드를 사용합니다.

MyAssembly.dll이 실행 중인 어셈블리 근처의 알려진 위치에 있다고 가정하면 어셈블리에 대한 경로가 지정된 Assembly.LoadFile을 사용하여 확인할 수 있습니다.

using System;
using System.IO;
using System.Reflection;

class CorrectExample
{
    static void Main()
    {
        AppDomain ad = AppDomain.CreateDomain("Test");
        ad.AssemblyResolve += MyHandler;

        try
        {
            object obj = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    static Assembly MyHandler(object source, ResolveEventArgs e)
    {
        Console.WriteLine("Resolving {0}", e.Name);

        var path = Path.GetFullPath("../../MyAssembly.dll");
        return Assembly.LoadFile(path);
     }
}
Imports System.IO
Imports System.Reflection

Class CorrectExample

    Shared Sub Main()

        Dim ad As AppDomain = AppDomain.CreateDomain("Test")
        AddHandler ad.AssemblyResolve, AddressOf MyHandler

        Try
            Dim obj As Object = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType")
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub

    Shared Function MyHandler(ByVal source As Object,
                              ByVal e As ResolveEventArgs) As Assembly
        Console.WriteLine("Resolving {0}", e.Name)

        Dim fullPath = Path.GetFullPath("../../MyAssembly.dll")
        Return Assembly.LoadFile(fullPath)
    End Function
End Class
using namespace System;
using namespace System::IO;
using namespace System::Reflection;

ref class Example
{
internal:
    static Assembly^ MyHandler(Object^ source, ResolveEventArgs^ e)
    {
        Console::WriteLine("Resolving {0}", e->Name);

        String^ fullPath = Path::GetFullPath("../../MyAssembly.dll");
        return Assembly::LoadFile(fullPath);
    }
};

void main()
{
    AppDomain^ ad = AppDomain::CreateDomain("Test");
    ad->AssemblyResolve += gcnew ResolveEventHandler(&Example::MyHandler);

    try
    {
        Object^ obj = ad->CreateInstanceAndUnwrap(
            "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
            "MyType");
    }
    catch (Exception^ ex)
    {
        Console::WriteLine(ex->Message);
    }
}

참고 항목