Udostępnij za pośrednictwem


Rozwiązywanie załadowań zestawów

Platforma .NET udostępnia zdarzenie AppDomain.AssemblyResolve dla aplikacji, które wymagają większej kontroli nad ładowaniem zestawów. Dzięki obsłudze tego zdarzenia aplikacja może załadować zestaw do kontekstu ładowania spoza normalnych ścieżek sondowania, wybrać kilka wersji zestawu do załadowania, emitować zestaw dynamiczny i zwracać go itd. Ten temat zawiera wskazówki dotyczące obsługi zdarzenia AssemblyResolve.

Uwaga / Notatka

W przypadku rozpoznawania obciążeń zestawów w kontekście tylko odbicia należy użyć AppDomain.ReflectionOnlyAssemblyResolve zdarzenia.

Jak działa zdarzenie AssemblyResolve

Podczas rejestrowania obsługi dla zdarzenia AssemblyResolve, program obsługi jest uruchamiany za każdym razem, gdy środowisko uruchomieniowe nie może powiązać się z zestawem po nazwie. Na przykład wywołanie z kodu użytkownika następujących metod może spowodować wystąpienie zdarzenia AssemblyResolve:

Co robi obsługa zdarzeń

Procedura obsługi zdarzenia AssemblyResolve odbiera nazwę wyświetlaną zestawu do załadowania we właściwości ResolveEventArgs.Name. Jeśli program obsługi nie rozpoznaje nazwy zestawu, zwraca wartość null (C#), Nothing (Visual Basic) lub nullptr (Visual C++).

Jeśli program obsługi rozpoznaje nazwę zestawu, może załadować i zwrócić zestaw spełniający żądanie. Poniższa lista zawiera opis niektórych przykładowych scenariuszy.

  • Jeśli program obsługi zna lokalizację wersji zestawu, może załadować zestaw, używając metody Assembly.LoadFrom lub Assembly.LoadFile, i może zwrócić załadowany zestaw, jeśli się powiedzie.

  • Jeśli obsługujący ma dostęp do bazy danych zestawów przechowywanych jako tablice bajtów, może załadować tablicę bajtów, korzystając z jednego z przeciążeń metody Assembly.Load, które odbierają tablicę bajtów.

  • Program obsługi może wygenerować zestaw dynamiczny i zwrócić go.

Uwaga / Notatka

Program obsługi musi załadować zestaw do kontekstu "load-from", do kontekstu ładowania lub bez kontekstu. Jeśli program obsługi ładuje zestaw do kontekstu tylko-do-odbicia, używając metody Assembly.ReflectionOnlyLoad lub Assembly.ReflectionOnlyLoadFrom, próba załadowania, która wywołała zdarzenie AssemblyResolve, zakończy się niepowodzeniem.

Jest to odpowiedzialność obsługującego zdarzenia za zwrócenie odpowiedniej kompilacji. Obsługiwarka może przeanalizować nazwę wyświetlaną żądanego zestawu, przekazując wartość właściwości ResolveEventArgs.Name do konstruktora AssemblyName(String). Począwszy od .NET Framework 4, program obsługi może użyć właściwości ResolveEventArgs.RequestingAssembly, aby określić, czy bieżące żądanie jest zależnością innego zestawu. Te informacje mogą pomóc w zidentyfikowaniu zestawu, który spełni zależność.

Program obsługi zdarzeń może zwrócić inną wersję zestawu niż żądana wersja.

W większości przypadków zestaw zwracany przez program obsługi pojawia się w kontekście ładowania, niezależnie od kontekstu, do którego program obsługi go ładuje. Na przykład, jeśli obsługujący używa metody Assembly.LoadFrom w celu załadowania zestawu do kontekstu z ładowania, zestaw pojawi się w kontekście ładowania, gdy obsługujący go zwróci. Jednak w następującym przypadku zestaw jest wyświetlany bez kontekstu, gdy program obsługi zwróci go:

Aby uzyskać informacje o kontekstach, zobacz przeciążenie metody Assembly.LoadFrom(String).

Wiele wersji tego samego zestawu można załadować do tej samej domeny aplikacji. Ta praktyka nie jest zalecana, ponieważ może prowadzić do problemów z przypisywaniem typów. Zobacz Najlepsze rozwiązania dotyczące ładowania zestawów.

Co program obsługi zdarzeń nie powinien wykonywać

Podstawową regułą obsługi AssemblyResolve zdarzenia jest to, że nie należy próbować zwracać zestawu, którego nie rozpoznajesz. Podczas pisania programu obsługi należy wiedzieć, które zestawy mogą powodować wywoływanie zdarzenia. Procedura obsługi powinna zwracać wartość null dla innych zestawów.

Ważne

Począwszy od .NET Framework 4, zdarzenie AssemblyResolve jest generowane dla zestawów satelitarnych. Ta zmiana dotyczy procedury obsługi zdarzeń napisanej dla starszej wersji .NET Framework, jeśli procedura próbuje rozwiązać wszystkie żądania ładowania zestawów. Programy obsługi zdarzeń, które nie rozpoznają i ignorują nieznane im zestawy, nie mają wpływu na tę zmianę: zwracają null, a normalne mechanizmy rezerwowe są przestrzegane.

Podczas ładowania zestawu program obsługi zdarzeń nie może używać żadnego z przeciążeń metody AppDomain.Load lub Assembly.Load , które mogą powodować rekursywne wywoływanie zdarzenia AssemblyResolve, ponieważ może to prowadzić do przepełnienia stosu. (Zobacz listę podaną wcześniej w tym temacie). Dzieje się tak nawet w przypadku podania obsługi wyjątków dla żądania ładowania, ponieważ nie jest zgłaszany żaden wyjątek do momentu zwrócenia wszystkich procedur obsługi zdarzeń. W związku z tym poniższy kod powoduje przepełnienie stosu, jeśli MyAssembly nie zostanie znalezione:

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.
*/

Prawidłowy sposób obsługi rozwiązania AssemblyResolve

Podczas rozpoznawania zestawów z AssemblyResolve programu obsługi zdarzeń w końcu zgłosi StackOverflowException, jeśli program obsługi wywołuje metodę Assembly.Load lub AppDomain.Load. Zamiast tego należy użyć metod LoadFile lub LoadFrom, ponieważ nie zgłaszają zdarzenia AssemblyResolve.

Wyobraź sobie, że MyAssembly.dll znajduje się w pobliżu wykonywanego zestawu, w znanej lokalizacji, można go rozpoznać przy użyciu Assembly.LoadFile podanej ścieżki do zestawu.

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

Zobacz także