Rozwiązywanie załadowań zestawów

Platforma .NET udostępnia zdarzenie dla aplikacji, które wymagają większej AppDomain.AssemblyResolve 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 AssemblyResolve zdarzenia.

Uwaga

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 programu obsługi dla AssemblyResolve zdarzenia program obsługi jest wywoływany za każdym razem, gdy środowisko uruchomieniowe nie będzie wiązać się z zestawem według nazwy. Na przykład wywołanie następujących metod z kodu użytkownika może spowodować AssemblyResolve wywołanie zdarzenia:

Co robi procedura obsługi zdarzeń

Procedura obsługi zdarzenia AssemblyResolve odbiera nazwę wyświetlaną zestawu do załadowania ResolveEventArgs.Name we właściwości . 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 przy użyciu Assembly.LoadFrom metody lub Assembly.LoadFile i może zwrócić załadowany zestaw w przypadku powodzenia.

  • Jeśli program obsługi ma dostęp do bazy danych zestawów przechowywanych jako tablice bajtów, może załadować tablicę bajtów przy użyciu jednego z Assembly.Load przeciążeń metody, które przyjmują tablicę bajtów.

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

Uwaga

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 odbicia przy użyciu Assembly.ReflectionOnlyLoad metody lub Assembly.ReflectionOnlyLoadFrom , próba załadowania, która wywołała AssemblyResolve zdarzenie, zakończy się niepowodzeniem.

Jest to odpowiedzialność programu obsługi zdarzeń w celu zwrócenia odpowiedniego zestawu. Procedura obsługi może przeanalizować nazwę wyświetlaną żądanego zestawu, przekazując ResolveEventArgs.Name wartość właściwości do konstruktora AssemblyName(String) . Począwszy od programu .NET Framework 4, program obsługi może użyć ResolveEventArgs.RequestingAssembly właściwości , 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. Jeśli na przykład program obsługi używa Assembly.LoadFrom metody w celu załadowania zestawu do kontekstu load-from, zestaw pojawi się w kontekście ładowania, gdy program obsługi zwróci go. 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 Assembly.LoadFrom(String) przeciążenie metody.

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 programu .NET Framework 4, AssemblyResolve zdarzenie jest wywoływane dla zestawów satelitarnych. Ta zmiana ma wpływ na program obsługi zdarzeń napisany dla starszej wersji programu .NET Framework, jeśli program obsługi próbuje rozwiązać wszystkie żądania ładowania zestawów. Programy obsługi zdarzeń, które ignorują zestawy, których nie rozpoznają, nie mają wpływu na tę zmianę: zwracają nullone i są przestrzegane normalne mechanizmy rezerwowe.

Podczas ładowania zestawu program obsługi zdarzeń nie może używać żadnego AppDomain.Load przeciążenia metody lub Assembly.Load , które mogą powodować AssemblyResolve rekursywne wywoływanie zdarzenia, 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 zostanie zgłoszony, StackOverflowException jeśli program obsługi używa Assembly.Load wywołań metody lub AppDomain.Load . Zamiast tego należy użyć LoadFile metod lub LoadFrom , ponieważ nie zgłaszają AssemblyResolve zdarzenia.

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 też