разрешение загрузки сборок

В .NET имеется событие AppDomain.AssemblyResolve для приложений, требующих дополнительного управления загрузкой сборок. Обрабатывая это событие, приложение может загружать сборку в контекст загрузки не из каталогов, где обычно осуществляется поиск, выбирать, какую из версий сборки загрузить, создавать динамическую сборку и возвращать ее и многое другое. В этом разделе описывается использование события AssemblyResolve.

Примечание.

Для разрешения загрузки сборок в контексте только для отражения используйте событие AppDomain.ReflectionOnlyAssemblyResolve.

Принцип действия события AssemblyResolve

При регистрации обработчика для события AssemblyResolve обработчик вызывается каждый раз, когда среда выполнения не может связать сборку по имени. Например, вызов следующих методов из пользовательского кода может привести к возникновению события AssemblyResolve.

  • Перегрузка метода AppDomain.Load или перегрузка метода Assembly.Load, где первым аргументом является строка, представляющая отображаемое имя загружаемой сборки (то есть строка, возвращаемая свойством Assembly.FullName).

  • Перегрузка метода AppDomain.Load или перегрузка метода Assembly.Load, где первым аргументом является объект AssemblyName, идентифицирующий загружаемую сборку.

  • Перегрузка метода Assembly.LoadWithPartialName.

  • Перегрузка метода AppDomain.CreateInstance или AppDomain.CreateInstanceAndUnwrap, создающая объект в другом домене приложения.

Действия обработчика событий

Обработчик события 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 для загрузки сборки в контекст, из которого ведется загрузка, сборка появляется в контексте загрузки, когда обработчик возвращает ее. Но в следующем случае возвращенная обработчиком сборка появится без контекста.

  • Обработчик загружает сборку без контекста.

  • Значение свойства ResolveEventArgs.RequestingAssembly отличается от NULL.

  • Запрашивающая сборка (то есть сборка, возвращенная свойством ResolveEventArgs.RequestingAssembly) загружена без контекста.

Сведения о контекстах см. в разделе о перегрузке метода Assembly.LoadFrom(String).

Несколько версий одной сборки можно загрузить в один домен приложения. Делать так не рекомендуется, так как это может привести к проблемам назначения типа. См. раздел Рекомендации для загрузки сборок.

Чего не должен делать обработчик событий

Основное правило обработки события AssemblyResolve заключается в том, что не следует пытаться вернуть сборку, которая не распознается. При написании обработчика следует учитывать, какие сборки могут вызвать событие. Обработчик должен возвращать значение NULL для других сборок.

Внимание

Начиная с .NET Framework 4 событие AssemblyResolve вызывается для вспомогательных сборок. Это изменение затрагивает обработчик событий, написанный для более ранней версии .NET Framework, если обработчик пытается разрешить все запросы на загрузку сборок. Обработчики событий, которые игнорируют сборки, которые они не распознают, не затрагиваются этим изменением: они возвращаются null, и следуют обычные резервные механизмы.

При загрузке сборки обработчик событий не должен использовать какую-либо из перегрузок метода AppDomain.Load или Assembly.Load, которые могут вызвать рекурсивное возникновение события AssemblyResolve, так как это может привести к переполнению стека. (См. список, указанный ранее в этом разделе.) Это происходит, даже если вы предоставляете обработку исключений для запроса загрузки, так как исключение не создается до тех пор, пока не будут возвращены все обработчики событий. Таким образом, следующий код приведет к переполнению стека, если объект 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 обработчика событий в конечном итоге создается, StackOverflowException если обработчик использует Assembly.Load вызовы или AppDomain.Load методы. Вместо этого используйте LoadFile или LoadFrom методы, так как они не вызывают AssemblyResolve событие.

Представьте, что 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);
    }
}

См. также