c# - ¿Puede una llamada a Assembly.Load(byte[]) generar el evento AppDomain.AssemblyResolve?
.net reflection (4)
Supongamos que tengo un controlador para el evento AppDomain.AssemblyResolve
, y en el controlador construyo una matriz de bytes e invoco el método Assembly.Load(byte[])
. ¿Puede este método hacer que el evento AssemblyResolve
vuelva a aparecer, y que se vuelva a ingresar a mi controlador?
Mi pregunta no está restringida solo a los ensamblajes que pueden generarse usando el compilador C #, pueden contener metadatos básicos y código ejecutable admitido por el CLR.
Hice algunos experimentos y no encontré ningún caso cuando sucedió. Intenté cargar ensamblajes que requieren referencias adicionales, intenté agregar atributos CAS al ensamblado cargado cuya decodificación requeriría otro ensamblaje, intenté cargar un ensamblaje con un inicializador de módulo (método .cctor
global). En ningún caso observé que el evento AssemblyResolve
se generara desde el método Assembly.Load(byte[])
, solo sucedió si algún código intentaba acceder posteriormente a tipos, métodos o atributos en el ensamblado cargado. Pero puedo estar perdiendo algo aquí.
La documentación de MSDN establece:
Cómo funciona el evento AssemblyResolve:
Cuando registra un controlador para el evento AssemblyResolve, el controlador se invoca cada vez que el tiempo de ejecución no se puede enlazar a un ensamblaje por su nombre. Por ejemplo, llamar a los siguientes métodos desde el código de usuario puede hacer que se genere 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 de visualización del ensamblaje 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 ensamblaje que se debe cargar.
No menciona la sobrecarga que recibe un byte[]
. Busqué en la fuente de referencia y parece que la Load
que acepta una sobrecarga de string
llama internamente a un método llamado InternalLoad
, que antes de invocar LoadImage
nativo llama a CreateAssemblyName
y su documentación indica:
Crea AssemblyName. Llena el ensamblaje si se ha levantado el evento AssemblyResolve.
internal static AssemblyName CreateAssemblyName(
String assemblyString,
bool forIntrospection,
out RuntimeAssembly assemblyFromResolveEvent)
{
if (assemblyString == null)
throw new ArgumentNullException("assemblyString");
Contract.EndContractBlock();
if ((assemblyString.Length == 0) ||
(assemblyString[0] == ''/0''))
throw new ArgumentException(Environment.GetResourceString("Format_StringZeroLength"));
if (forIntrospection)
AppDomain.CheckReflectionOnlyLoadSupported();
AssemblyName an = new AssemblyName();
an.Name = assemblyString;
an.nInit(out assemblyFromResolveEvent, forIntrospection, true); // This method may internally invoke AssemblyResolve event.
return an;
La sobrecarga de byte[]
no tiene esto, simplemente llama a la nLoadImage
nativa dentro de QCall.dll
. Esto puede explicar por qué ResolveEvent
no se invoca.
Mencionaste -
En ningún caso observé que el evento AssemblyResolve se generara desde el método Assembly.Load (byte []), solo sucedió si algún código intentaba acceder posteriormente a tipos, métodos o atributos en el ensamblado cargado. Pero puedo estar perdiendo algo aquí.
Los puntos a tener en cuenta aquí -
Mientras se ejecuta el código, si se hace referencia a un tipo en el código y CLR detecta que el ensamblaje que contiene el tipo no está cargado, cargará el ensamblaje. Su observación es correcta aquí.
AssemblyResolve es un evento definido en el tipo AppDomain. Por lo tanto, este evento no se puede generar desde el interior de la asamblea. Carga (byte [])
Por lo tanto, si ya se ha registrado con el evento AssemblyResolve en la aplicación en ejecución y llame al Ensamblaje. Carga (byte []) carga el ensamblaje en el dominio actual.
Ahora, cuando se invoca cualquier tipo de este conjunto cargado que diga que sucede que está llamando a otro tipo definido en otro conjunto, AppDomain llamará al evento AssemblyResolve para intentar cargar ese conjunto .
Por lo que sé, Assembly.Load
Cargar o cargar el ensamblaje por otros medios no ejecuta ningún constructor que pueda ser generado por el compilador de C # (incluidos los constructores estáticos). Como resultado, no volverá a entrar en AssemblyResolve
en AssemblyResolve
comunes.
Como mencionó en la pregunta, los inicializadores de módulo no se ejecutan durante la llamada de Load
. Cubierto en la lista de garantías en la especificación de CLI: el extracto se puede encontrar en Inicializadores de módulo por Junfeng Zhang.
B. El método de inicialización del módulo se ejecuta en, o en algún momento antes, el primer acceso a cualquier tipo, método o datos definidos en el módulo.
Por lo general, hay preguntas de SO relacionadas que tratan sobre "ejecutar código antes de cualquier tipo de constructores", como Inicializar biblioteca en la carga de ensamblados . Tenga en cuenta que .Net: el código en ejecución cuando se carga el ensamblaje tiene una respuesta de Marc Gravell que indica que puede no ser posible debido a restricciones de seguridad.
Un inicializador de módulo es el único creador de problemas que se me ocurre. Un ejemplo simple de uno en C ++ / CLI:
#include "stdafx.h"
#include <msclr/gcroot.h>
using namespace msclr;
using namespace ClassLibrary10;
class Init {
gcroot<ClassLibrary1::Class1^> managedObject;
public:
Init() {
managedObject = gcnew ClassLibrary1::Class1;
}
} Initializer;
El constructor Init () se invoca cuando el módulo se carga a través del inicializador del módulo, justo después de que inicializa el tiempo de ejecución de C. Usted está descolgado en este tipo de código, sin embargo, en su caso específico, Assembly.Load (byte []) no es capaz de cargar ensamblados de modo mixto.
De lo contrario no es una restricción inducida por los inicializadores de módulo. Se agregaron en CLR v2.0 con la intención específica de realizar trabajos similares como este, logrando que el tiempo de ejecución de un idioma se inicialice antes de que comience a ejecutar cualquier código administrado. Las probabilidades de que se encuentre con dicho código deberían ser muy, muy bajas. Lo sabrás cuando lo veas :)