Resolución de cargas de ensamblado
.NET proporciona el evento AppDomain.AssemblyResolve para aplicaciones que requieren un mayor control sobre la carga de ensamblados. Al controlar este evento, la aplicación puede cargar un ensamblado en el contexto de carga desde fuera de las rutas de acceso de sondeo normales, seleccionar qué versión de ensamblado cargar, emitir un ensamblado dinámico y devolverlo, etc. En este tema se proporcionan instrucciones para controlar el evento AssemblyResolve.
Nota
Para resolver cargas de ensamblado en el contexto de solo reflexión, use en su lugar el evento AppDomain.ReflectionOnlyAssemblyResolve.
Cómo funciona el evento AssemblyResolve
Al registrar un controlador para el evento AssemblyResolve, se invoca el controlador si se produce un error cuando el tiempo de ejecución enlaza a un ensamblado por nombre. Por ejemplo, si se llama a los métodos siguientes desde el código de usuario, puede producirse el evento AssemblyResolve:
Una sobrecarga del método AppDomain.Load o una sobrecarga del método Assembly.Load cuyo primer argumento es una cadena que representa el nombre para mostrar del ensamblado que se va a cargar (es decir, la cadena devuelta por la propiedad Assembly.FullName).
Una sobrecarga del método AppDomain.Load o una sobrecarga del método Assembly.Load cuyo primer argumento es un objeto AssemblyName que identifica el ensamblado que se va a cargar.
Una sobrecarga del método Assembly.LoadWithPartialName.
Una sobrecarga del método AppDomain.CreateInstance o AppDomain.CreateInstanceAndUnwrap que crea instancias de un objeto en otro dominio de aplicación.
Qué hace el controlador de eventos
El controlador del evento AssemblyResolve recibe el nombre para mostrar del ensamblado que se va a cargar, en la propiedad ResolveEventArgs.Name. Si el controlador no reconoce el nombre del ensamblado, devuelve null
(C#), Nothing
(Visual Basic) o nullptr
(Visual C++).
Si el controlador reconoce el nombre del ensamblado, puede cargar y devolver un ensamblado que cumpla la solicitud. En la lista siguiente se describen algunos escenarios de ejemplo.
Si el controlador conoce la ubicación de una versión del ensamblado, puede cargar el ensamblado mediante el método Assembly.LoadFrom o Assembly.LoadFile y devolver el ensamblado cargado si se realiza correctamente.
Si el controlador tiene acceso a una base de datos de los ensamblados almacenados como matrices de bytes, puede cargar una matriz de bytes mediante una de las sobrecargas del método Assembly.Load que toman una matriz de bytes.
El controlador puede generar un ensamblado dinámico y devolverlo.
Nota
El controlador debe cargar el ensamblado en el contexto de origen de carga, en el contexto de carga o sin contexto. Si el controlador carga el ensamblado en el contexto de solo reflexión mediante Assembly.ReflectionOnlyLoad o el método Assembly.ReflectionOnlyLoadFrom, el intento de carga que ha generado el evento AssemblyResolve sufre un error.
El controlador de eventos se encarga de devolver un ensamblado adecuado. El controlador puede analizar el nombre para mostrar del ensamblado solicitado. Para ello, pasa el valor de propiedad ResolveEventArgs.Name al constructor AssemblyName(String). A partir de .NET Framework 4, el controlador puede usar la propiedad ResolveEventArgs.RequestingAssembly para determinar si la solicitud actual es una dependencia de otro ensamblado. Esta información puede ayudar a identificar un ensamblado que cumplirá la dependencia.
El controlador de eventos puede devolver una versión del ensamblado diferente de la versión solicitada.
En la mayoría de los casos, el ensamblado que el controlador devuelve aparece en el contexto de carga, independientemente del contexto en el que lo carga el controlador. Por ejemplo, si el controlador usa el método Assembly.LoadFrom para cargar un ensamblado en el contexto de origen de carga, el ensamblado aparece en el contexto de carga cuando el controlador lo devuelve. Pero en el caso siguiente, el ensamblado aparece sin contexto cuando el controlador lo devuelve:
El controlador carga un ensamblado sin contexto.
La propiedad ResolveEventArgs.RequestingAssembly no es NULL.
El ensamblado solicitante (es decir, el ensamblado devuelto por la propiedad ResolveEventArgs.RequestingAssembly) se ha cargado sin contexto.
Para obtener información sobre los contextos, vea la sobrecarga del método Assembly.LoadFrom(String).
Es posible cargar varias versiones del mismo ensamblado en el mismo dominio de aplicación, pero esta práctica no se recomienda porque puede provocar problemas de asignación de tipos. Vea Procedimientos recomendados para cargar ensamblados.
Qué no debe hacer el controlador de eventos
La regla principal que debe observar para controlar el evento AssemblyResolve es que no debe intentar devolver un ensamblado que no reconozca. Cuando se escribe el controlador, debe saber qué ensamblados pueden hacer que se produzca el evento. El controlador debe devolver NULL para otros ensamblados.
Importante
A partir de .NET Framework 4, se genera el evento AssemblyResolve para los ensamblados satélite. Este cambio afecta a los controladores de eventos escritos para versiones anteriores de .NET Framework si intentan resolver todas las solicitudes de carga del ensamblado. Este cambio no afecta a los controladores de eventos que omiten los ensamblados que no reconocen: devuelven null
y se siguen los mecanismos normales de reserva.
Al cargar un ensamblado, el controlador de eventos no debe usar las sobrecargas del método AppDomain.Load o Assembly.Load que pueden hacer que se produzca el evento AssemblyResolve de forma recursiva, ya que esto puede provocar un desbordamiento de pila. (Vea la lista proporcionada anteriormente en este tema). Esto ocurre incluso si proporciona el control de excepciones para la solicitud de carga, porque no se produce ninguna excepción hasta que se hayan devuelto todos los controladores de eventos. Así pues, el código siguiente produce un desbordamiento de pila si no se encuentra 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.
*/
Manera correcta de controlar AssemblyResolve
Al resolver ensamblados desde el controlador de eventos AssemblyResolve, al final se inicia una excepción StackOverflowException si el controlador usa las llamadas al método Assembly.Load o AppDomain.Load. En su lugar, use métodos LoadFile o LoadFrom, ya que no generan el evento AssemblyResolve
.
Imagine que MyAssembly.dll
se encuentra cerca del ensamblado en ejecución, en una ubicación conocida, se puede resolver mediante Assembly.LoadFile
dada la ruta de acceso al ensamblado.
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);
}
}