winform usuario tiempo por net mover ejecucion dinamicos dinamicamente crear controles control codigo botones agregar c# reflection cil reflection.emit il

usuario - crear textbox dinamicos c#



Creando método dinámicamente y ejecutándolo (6)

Fondo:

Quiero definir algunos métodos static en C # y generar código IL como matriz de bytes, a partir de uno de estos métodos, seleccionado en tiempo de ejecución (en el cliente), y enviar la matriz de bytes a otra máquina (servidor) donde debe ejecutarse después de volver a generar el código IL de la matriz de bytes.

Mi intento: ( POC )

public static class Experiment { public static int Multiply(int a, int b) { Console.WriteLine("Arguments ({0}, {1})", a, b); return a * b; } }

Y luego obtengo el código IL del cuerpo del método, como:

BindingFlags flags = BindingFlags.Public | BindingFlags.Static; MethodInfo meth = typeof(Experiment).GetMethod("Multiply", flags); byte[] il = meth.GetMethodBody().GetILAsByteArray();

Hasta ahora no he creado nada dinámicamente. Pero tengo el código IL como matriz de bytes, y quiero crear un ensamblaje, luego un módulo, luego un tipo, luego un método, todo dinámicamente. Al crear el cuerpo del método del método creado dinámicamente, utilizo el código IL que obtuve utilizando la reflexión en el código anterior.

El código de generación de código es el siguiente:

AppDomain domain = AppDomain.CurrentDomain; AssemblyName aname = new AssemblyName("MyDLL"); AssemblyBuilder assemBuilder = domain.DefineDynamicAssembly( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule("MainModule"); TypeBuilder tb = modBuilder.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); MethodBuilder mb = tb.DefineMethod("MyMethod", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(int), // Return type new[] { typeof(int), typeof(int) }); // Parameter types mb.DefineParameter(1, ParameterAttributes.None, "value1"); // Assign name mb.DefineParameter(2, ParameterAttributes.None, "value2"); // Assign name //using the IL code to generate the method body mb.CreateMethodBody(il, il.Count()); Type realType = tb.CreateType(); var meth = realType.GetMethod("MyMethod"); try { object result = meth.Invoke(null, new object[] { 10, 9878 }); Console.WriteLine(result); //should print 98780 (i.e 10 * 9878) } catch (Exception e) { Console.WriteLine(e.ToString()); }

Pero en lugar de imprimir 98780 en la ventana de salida, arroja un refrán de excepción,

System.Reflection.TargetInvocationException: la excepción ha sido lanzada por el objetivo de una invocación. ---> System.TypeLoadException: No se pudo cargar el tipo ''Invalid_Token.0x0100001E'' del ensamblado ''MyDLL, Version = 0.0.0.0, Culture = neutral, PublicKeyToken = null''.
en MyType.MyMethod (Int32 value1, Int32 value2) [...]

Por favor, ayúdame a descifrar la causa del error y cómo solucionarlo.


Creo que el problema tiene que ver con el uso de IL de un tipo / ensamblado en otro. Si reemplazas esto:

mb.CreateMethodBody(il, il.Count());

con este:

ILGenerator generator = mb.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Mul); generator.Emit(OpCodes.Ret);

luego ejecutará el método correctamente (sin Console.WriteLine , pero devuelve el valor correcto).

Si realmente necesita poder extraer IL de un método existente, tendrá que buscar más, pero si solo necesitara la validación de que el resto del código estaba funcionando, esto podría ayudar.

Una cosa que puede interesarle es que el error cambie en su código original si saca la llamada Console.WriteLine de Experiment . Se convierte en una InvalidProgramException en InvalidProgramException lugar. No tengo idea de por qué ...


Ejecute ildasm.exe en un ensamblaje arbitrario. Use los valores de token View + Show y observe algún código desmontado. Verá que el IL contiene referencias a otros métodos y variables a través de un número. El número es un índice en las tablas de metadatos para un ensamblaje.

Quizás ahora vea el problema, no puede trasplantar un trozo de IL de un ensamblaje a otro a menos que ese ensamblaje de destino tenga los mismos metadatos . O a menos que reemplace los valores del token de metadatos en el IL con valores que coincidan con los metadatos del ensamblaje de destino. Esto es muy poco práctico, por supuesto, esencialmente terminas duplicando el ensamblaje. Bien podría hacer una copia de la asamblea. O para el caso, también podría usar el IL existente en el ensamblaje original.

Debes pensar esto un poco, no está muy claro lo que realmente intentas lograr. Los espacios de nombres System.CodeDom y Reflection.Emit están disponibles para generar código dinámicamente.


Esta respuesta es un poquito ortogonal, más sobre el problema que la tecnología involucrada.

Podrías usar árboles de expresión, son agradables para trabajar y tienen azúcar sintáctico VS.

Para la serialización necesita esta biblioteca (deberá compilarla también): http://expressiontree.codeplex.com/ (Esto también funciona con Silverlight 4, aparentemente).

La limitación de los árboles de expresiones es que solo admiten expresiones Lambda, es decir, sin bloques.

Esto no es realmente una limitación porque todavía puede definir otros métodos lambda dentro de un lambda y pasarlos a ayudantes de programación funcional como la API de Linq.

He incluido un método de extensión simple para mostrarte cómo agregar otros métodos auxiliares para cosas funcionales: el punto clave es que debes incluir los ensamblajes que contienen las extensiones en tu serializador.

Este código funciona:

using System; using System.Linq; using System.Linq.Expressions; using ExpressionSerialization; using System.Xml.Linq; using System.Collections.Generic; using System.Reflection; namespace ExpressionSerializationTest { public static class FunctionalExtensions { public static IEnumerable<int> to(this int start, int end) { for (; start <= end; start++) yield return start; } } class Program { private static Expression<Func<int, int, int>> sumRange = (x, y) => x.to(y).Sum(); static void Main(string[] args) { const string fileName = "sumRange.bin"; ExpressionSerializer serializer = new ExpressionSerializer( new TypeResolver(new[] { Assembly.GetExecutingAssembly() }) ); serializer.Serialize(sumRange).Save(fileName); Expression<Func<int, int, int>> deserializedSumRange = serializer.Deserialize<Func<int, int, int>>( XElement.Load(fileName) ); Func<int, int, int> funcSumRange = deserializedSumRange.Compile(); Console.WriteLine( "Deserialized func returned: {0}", funcSumRange(1, 4) ); Console.ReadKey(); } } }


Hay una manera difícil de hacer que el método de "copia" funcione y se tome un tiempo.

Eche un vistazo a ILSpy , esta aplicación se utiliza para ver y analizar el código existente y es de código abierto. Puede extraer el código del proyecto que se utiliza para analizar el código IL-ASM y usarlo para copiar el método.


Si entiendo bien tu problema y solo quieres generar dinámicamente algún código .NET y ejecutarlo en un cliente remoto, maeby echa un vistazo a IronPython. Solo necesita crear cadena con script y luego solo enviarla al cliente, y el cliente puede ejecutarla en tiempo de ejecución con acceso a .NET Framework, incluso interferir con su aplicación cliente en tiempo de ejecución.


Si tomo el siguiente método:

public static int Multiply(int a, int b) { return a * b; }

compilarlo en modo Release y usarlo en tu código, todo funciona bien. Y si inspecciono el conjunto il , contiene 4 bytes que corresponden exactamente a las cuatro instrucciones de la respuesta de Jon (ldarg.0, ldarg.1, mul, ret).

Si lo compilo en modo de depuración, el código es de 9 bytes. Y en Reflector, se ve así:

.method public hidebysig static int32 Multiply(int32 a, int32 b) cil managed { .maxstack 2 .locals init ( [0] int32 CS$1$0000) L_0000: nop L_0001: ldarg.0 L_0002: ldarg.1 L_0003: mul L_0004: stloc.0 L_0005: br.s L_0007 L_0007: ldloc.0 L_0008: ret }

La parte problemática es la variable local. Si solo copia los bytes de instrucción, emite instrucciones que usan una variable local, pero nunca la declara.

Si usaba ILGenerator , podría usar DeclareLocal() . Parece que podrá establecer variables locales usando MethodBuilder.SetMethodBody() en .Net 4.5 (lo siguiente funciona para mí en VS 11 DP):

var module = typeof(Experiment).Module; mb.SetMethodBody(il, methodBody.MaxStackSize, module.ResolveSignature(methodBody.LocalSignatureMetadataToken), null, null);

Pero no he encontrado una manera de hacerlo en .Net 4, excepto mediante el uso de la reflexión para establecer un campo privado de MethodBuilder , que contiene las variables locales, después de llamar a CreateMethodBody() :

typeof(MethodBuilder) .GetField("m_localSignature", BindingFlags.NonPublic | BindingFlags.Instance) .SetValue(mb, module.ResolveSignature(methodBody.LocalSignatureMetadataToken));

Respecto al error original: los tipos y métodos de otros ensambles (como System.Console y System.Console.WriteLine ) se referencian usando tokens. Y esos tokens son diferentes de ensamblaje a ensamblado. Eso significa que el código para llamar a Console.WriteLine() en un ensamblado será diferente del código para llamar al mismo método en otro ensamblado, si observa los bytes de la instrucción.

Lo que eso significa es que tendrías que entender realmente lo que significan los bytes de IL y reemplazar todos los tokens que hacen referencia a los tipos, métodos, etc., a los que son válidos en el ensamblado que estás construyendo.