2009-09-01 7 views
11

¿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.

Respuesta

1

Probablemente no, porque un lambda es algo más que una expresión en código fuente. Las expresiones lambda también crean cierres que capturan/elevan variables en sus propias clases ocultas. El programa es modificado por el compilador, por lo que en todos los lugares donde usa esas variables, en realidad está hablando con la clase. Por lo tanto, no solo debe pasar el código de la lambda, sino también cualquier cambio en las variables de cierre a lo largo del tiempo.

+0

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

+0

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. –

2

No he tenido exactamente cuál es el problema que está tratando de resolver, pero hice un componente en el pasado que puede resolverlo.

Básicamente, su objetivo era generar una expresión lambda de una string. Utiliza un AppDomain por separado para ejecutar el compilador CodeDOM. El IL de un método compilado se serializa al original AppDomain y luego se reconstruye a un delegado con DynamicMethod. Luego, se llama al delegado y se devuelve una expresión lambda.

He publicado una explicación completa de ella en mi blog. Naturalmente, es de código abierto. Entonces, si puede usarlo, por favor envíeme cualquier comentario que considere razonable.

+0

¡Quise inyectar código de mi AppDomain en mi AppDomain de extensión VS principal y esto funcionó maravillosamente! su información a cualquier otra persona que quiera utilizar esto para código existente, tendrá que hacer cambios para reconstruir el DynamicMethod del MethodIL pasar por el módulo que contiene todos los tipos de excepciones para evitar el acceso/seguridad. – mstrange

+0

@mstrange Me alegra saber que esto sigue siendo útil después de más de 6 años. – jpbochi

Cuestiones relacionadas