Partilhar via


Resolver cargas de montagem

O .NET fornece o evento AppDomain.AssemblyResolve para aplicações que exigem um maior controle sobre o carregamento de assemblagem. Ao manipular esse evento, seu aplicativo pode carregar um assembly no contexto de carga de fora dos caminhos de sondagem normais, selecionar qual das várias versões de assembly carregar, emitir um assembly dinâmico e retorná-lo, e assim por diante. Este tópico fornece orientação para lidar com o AssemblyResolve evento.

Observação

Para resolver cargas de assembly no contexto de reflexão apenas, utilize o evento AppDomain.ReflectionOnlyAssemblyResolve.

Como funciona o evento AssemblyResolve

Quando você registra um manipulador para o AssemblyResolve evento, o manipulador é invocado sempre que o tempo de execução não consegue se vincular a um assembly pelo nome. Por exemplo, chamar os seguintes métodos do código do usuário pode fazer com que o AssemblyResolve evento seja gerado:

O que o manipulador de eventos faz

O manipulador para o evento AssemblyResolve recebe o nome a apresentar do assembly que será carregado, na propriedade ResolveEventArgs.Name. Se o manipulador não reconhecer o nome do assembly, ele retornará null (C#), Nothing (Visual Basic) ou nullptr (Visual C++).

Se o manipulador reconhecer o nome do assembly, ele poderá carregar e retornar um assembly que satisfaça a solicitação. A lista a seguir descreve alguns cenários de exemplo.

  • Se o manipulador souber a localização de uma versão do assembly, poderá carregá-la usando o método Assembly.LoadFrom ou Assembly.LoadFile e poderá retornar a assembly carregada se for bem-sucedido.

  • Se o manipulador tiver acesso a um banco de dados de assemblies armazenados como matrizes de bytes, poderá carregar uma matriz de bytes usando uma das sobrecargas de método que aceitam uma matriz de bytes.

  • O manipulador pode gerar um conjunto dinâmico e devolvê-lo.

Observação

O manipulador deve carregar o assembly no contexto de load-from, no contexto de load ou sem contexto. Se o manipulador carregar o assembly no contexto somente de reflexão usando o Assembly.ReflectionOnlyLoad ou Assembly.ReflectionOnlyLoadFrom o método, a tentativa de carregamento que gerou o evento AssemblyResolve falhará.

É responsabilidade do manipulador de eventos retornar uma montagem adequada. O manipulador pode analisar o nome de exibição do assembly solicitado ao passar o valor da propriedade ResolveEventArgs.Name para o construtor AssemblyName(String). A partir do .NET Framework 4, o manipulador pode usar a ResolveEventArgs.RequestingAssembly propriedade para determinar se a solicitação atual é uma dependência de outro assembly. Essas informações podem ajudar a identificar um assembly que possa satisfazer a dependência.

O manipulador de eventos pode retornar uma versão diferente do assembly daquela que foi solicitada.

Na maioria dos casos, o assembly retornado pelo manipulador aparece no contexto de carga, independentemente do contexto em que o manipulador o carrega. Por exemplo, se o handler usa o método Assembly.LoadFrom para carregar um assembly no contexto load-from, o assembly aparece no contexto de load quando é devolvido pelo handler. No entanto, no seguinte caso, o assembly aparece sem contexto quando o manipulador o retorna:

Para obter informações sobre contextos, consulte a sobrecarga do método Assembly.LoadFrom(String).

Várias versões do mesmo assembly podem ser carregadas no mesmo domínio do aplicativo. Esta prática não é recomendada, porque pode levar a problemas de atribuição de tipo. Consulte Práticas recomendadas para carregamento de montagem.

O que o manipulador de eventos não deve fazer

A regra principal para manipular o AssemblyResolve evento é que você não deve tentar retornar um assembly que não reconhece. Ao escrever o manipulador, você deve saber quais assemblies podem fazer com que o evento seja gerado. O seu manipulador deve retornar null para outros assemblies.

Importante

Começando com o .NET Framework 4, o AssemblyResolve evento é gerado para assemblies satélite. Essa alteração afeta um manipulador de eventos que foi escrito para uma versão anterior do .NET Framework, se o manipulador tenta resolver todas as solicitações de carga de assembly. Os manipuladores de eventos que ignoram assemblies que não reconhecem não são afetados por essa alteração: eles retornam nulle os mecanismos de fallback normais são seguidos.

Ao carregar um assembly, o manipulador de eventos não deve usar nenhuma das sobrecargas de método AppDomain.Load ou Assembly.Load que podem fazer com que o evento AssemblyResolve seja gerado recursivamente, porque isso pode levar a um estouro de pilha. (Consulte a lista fornecida anteriormente neste tópico.) Isso acontece mesmo se você fornecer tratamento de exceção para a solicitação de carga, porque nenhuma exceção é lançada até que todos os manipuladores de eventos tenham retornado. Assim, o código a seguir resulta em uma sobrecarga de pilha se MyAssembly não for encontrado:

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

A maneira correta de lidar com AssemblyResolve

Ao resolver assemblies a partir do manipulador de eventos AssemblyResolve, um StackOverflowException será eventualmente lançado se o manipulador usar as chamadas dos métodos Assembly.Load ou AppDomain.Load. Em vez disso, use LoadFile ou LoadFrom métodos, pois eles não geram o AssemblyResolve evento.

Imagine que MyAssembly.dll está localizado perto do assembly em execução, num local conhecido, o mesmo pode ser resolvido usando Assembly.LoadFile dado o caminho até o assembly.

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

Ver também