¿Es posible pasar una expresión lambda a un dominio de aplicación secundaria, como una corriente de bytes IL y luego montar de nuevo allí utilizando DynamicMethod por lo que puede ¿ser llamado?Pasar un lambda a un dominio de aplicación secundaria, como una corriente de IL y montaje de nuevo usando DynamicMethod
No estoy muy seguro de que este es el camino correcto a seguir en el primer lugar, así que aquí está la razón (detallado) hago esta pregunta ...
En mis aplicaciones, hay una gran cantidad de casos cuando necesito cargar un par de conjuntos para la reflexión, entonces puedo determinar qué hacer con ellos a continuación. El problema es que necesito poder descargar los ensambles cuando termine de reflexionar sobre ellos. Esto significa que necesito cargarlos usando otro AppDomain
.
Ahora, la mayoría de mis casos son similares, excepto que no del todo. Por ejemplo, a veces necesito devolver una confirmación simple, otras veces necesito serializar un flujo de recursos desde el ensamblado, y de nuevo otras veces necesito hacer una devolución de llamada o dos.
Así que termino escribiendo el mismo código de creación semi complicado AppDomain
una y otra vez e implementando proxies personalizados MarshalByRefObject
para comunicarme entre el nuevo dominio y el original.
Como esto no es realmente aceptable más, me decidieron a mi dirección código de una clase AssemblyReflector
que podría ser utilizado de esta manera:
using (var reflector = new AssemblyReflector(@"C:\MyAssembly.dll"))
{
bool isMyAssembly = reflector.Execute(assembly =>
{
return assembly.GetType("MyAssembly.MyType") != null;
});
}
AssemblyReflector
sería automatizar la AppDomain
descarga en virtud de IDisposable
, y me permitirá ejecutar un Func<Assembly,object>
-type lambda sosteniendo el código de reflexión en otro AppDomain
de forma transparente.
El problema es que las lambdas no se pueden pasar a otros dominios tan simplemente. Entonces, después de buscar, encontré lo que parece una forma de hacer precisamente eso: pasar el lambda al nuevo AppDomain
como una secuencia IL, y eso me lleva a la pregunta original.
Esto es lo que he intentado, pero no funcionó (el problema se BadImageFormatException
ser arrojado al intentar llamar al nuevo delegado):
public delegate object AssemblyReflectorDelegate(Assembly reflectedAssembly);
public class AssemblyReflector : IDisposable
{
private AppDomain _domain;
private string _assemblyFile;
public AssemblyReflector(string fileName) { ... }
public void Dispose() { ... }
public object Execute(AssemblyReflectorDelegate reflector)
{
var body = reflector.Method.GetMethodBody();
_domain.SetData("IL", body.GetILAsByteArray());
_domain.SetData("MaxStackSize", body.MaxStackSize);
_domain.SetData("FileName", _assemblyFile);
_domain.DoCallBack(() =>
{
var il = (byte[])AppDomain.CurrentDomain.GetData("IL");
var stack = (int)AppDomain.CurrentDomain.GetData("MaxStackSize");
var fileName = (string)AppDomain.CurrentDomain.GetData("FileName");
var args = Assembly.ReflectionOnlyLoadFrom(fileName);
var pars = new Type[] { typeof(Assembly) };
var dm = new DynamicMethod("", typeof(object), pars,
typeof(string).Module);
dm.GetDynamicILInfo().SetCode(il, stack);
var clone = (AssemblyReflectorDelegate)dm.CreateDelegate(
typeof(AssemblyReflectorDelegate));
var result = clone(args); // <-- BadImageFormatException thrown.
AppDomain.CurrentDomain.SetData("Result", result);
});
// Result obviously needs to be serializable for this to work.
return _domain.GetData("Result");
}
}
Soy yo ni siquiera cerca (lo que falta?), O se trata de un ejercicio inútil en general?
NOTA: Me doy cuenta de que si esto funcionara, igual tendría que tener cuidado con lo que puse en lambda con respecto a las referencias. Sin embargo, eso no es un problema.
ACTUALIZACIÓN: Logré llegar un poco más lejos. Parece que simplemente llamando al SetCode(...)
no es suficiente para reconstruir el método. Aquí está lo que se necesita:
// Build a method signature. Since we know which delegate this is, this simply
// means adding its argument types together.
var builder = SignatureHelper.GetLocalVarSigHelper();
builder.AddArgument(typeof(Assembly), false);
var signature = builder.GetSignature();
// This is the tricky part... See explanation below.
di.SetCode(ILTokenResolver.Resolve(il, di, module), stack);
dm.InitLocals = initLocals; // Value gotten from original method's MethodInfo.
di.SetLocalSignature(signature);
El truco son los siguientes. Original IL contiene ciertos tokens de metadatos que son válidos solo en el contexto del método original. Necesitaba analizar el IL y reemplazar esos tokens con los que son válidos en el nuevo contexto. Hice esto usando una clase especial, ILTokenResolver
, que adapté de estas dos fuentes: Drew Wilson y Haibo Luo.
Todavía hay un pequeño problema con esto: la nueva IL no parece ser exactamente válida.Dependiendo del contenido exacto de la lambda, puede o no arrojar una InvalidProgramException en tiempo de ejecución.
Como un simple ejemplo, esto funciona:
reflector.Execute(a => { return 5; });
mientras que esto no es así:
reflector.Execute(a => { int a = 5; return a; });
Hay también ejemplos más complejos que están trabajando ya sea o no, dependiendo de alguna todavía- diferencia a ser determinada. Podría ser que me perdí algunos detalles pequeños pero importantes. Pero estoy razonablemente seguro de que lo encontraré después de una comparación más detallada de las salidas ildasm. Publicaré mis hallazgos aquí, cuando lo haga.
EDIT: Oh, hombre. Olvidé completamente que esta pregunta todavía estaba abierta. Pero como probablemente se volvió obvio en sí mismo, desistí de resolver esto. No estoy contento con eso, eso es seguro. Es realmente una pena, pero creo que esperaré a recibir una mejor asistencia del framework y/o CLR antes de volver a intentarlo. Solo hay que hacer muchos hacks para que esto funcione, y aun así no es confiable. Disculpas a todos los interesados.
Lo sabía. ¿Pero pensé que eso no sería un problema si no usara variables externas en lambda? ¿Qué tal si utilizo un antiguo delegado simple en lugar de lambda? – aoven
Parte del punto es que un lambda no es solo el código en la expresión. También transforma otro código en su aplicación en tiempo de compilación. Puede transferir cambios en tiempo de compilación a otro ensamblado ya compilado. –