动态创建一个方法并执行它背景:我想在C#中定义一些静态方法,并在运行时(在客户端)从这些方法中选择一个生成IL代码作为字节数组,并在通过网络将字节数组发送到另一台应该执行它的机器(服务器)后,从字节数组重新生成IL代码。我的尝试:(POC)publicstaticclassExperiment{publicstaticintMultiply(inta,intb){Console.WriteLine("Arguments({0},{1})",a,b);返回一个*b;然后得到方法体的IL代码,如:BindingFlagsflags=BindingFlags.Public|BindingFlags.Static;MethodInfometh=typeof(Experiment).GetMethod("Multiply",flags);byte[]il=meth.GetMethodBody().GetILAsByteArray();到目前为止,我还没有动态创建任何东西。但是我有IL代码作为字节数组,我想创建一个程序集,然后是一个模块,然后是一个类型,然后是一个方法——都是动态的。在创建动态创建方法的方法体时,我在上面的代码中使用了使用反射获得的IL代码。代码生成代码如下:AppDomaindomain=AppDomain.CurrentDomain;AssemblyNameaname=newAssemblyName("MyDLL");AssemblyBuilderassemBuilder=domain.DefineDynamicAssembly(aname,AssemblyBuilderAccess.Run);ModuleBuildermodBuilder=assemBuilder.DefineDynamicModule("MainModule");TypeBuildertb=modBuilder.DefineType("MyType",TypeAttributes.Public|TypeAttributes.Class);MethodBuildermb=tb.DefineMethod("MyMethod",MethodAttributes.Static|MethodAttributes.Public,CallingConventions.Standard,typeof(int),//返回类型new[]{typeof(int),typeof(int)});//参数类型mb.DefineParameter(1,ParameterAttributes.None,"value1");//分配名称mb.DefineParameter(2,ParameterAttributes.None,"value2");//分配名称//使用IL代码生成方法体mb.CreateMethodBody(il,il.Count());类型realType=tb.CreateType();varmeth=realType.GetMethod("MyMethod");try{objectresult=meth.Invoke(null,newobject[]{10,9878});控制台.WriteLine(结果);//应该打印98780(即10*9878)}catch(Exceptione){Console.WriteLine(e.ToString());但是,它没有在输出窗口上打印98780,而是抛出了一个异常,说System.Reflection.TargetInvocationException:调用目标抛出异常-->System.TypeLoadException:无法从程序集加载类型“Invalid_Token.0x0100001E”MyDLL,Version=0.0.0.0,Culture=neutral,PublicKeyToken=null"".atMyType.MyMethod(Int32value1,Int32value2)[...]请帮我找出错误的原因,以及如何解决它.在任意程序集上运行ildasm.exe。使用View+Show标记值并查看一些反汇编代码。你会看到IL包含对其他方法和变量的引用编号。这个编号是程序集元数据表的索引。也许您现在已经看到了问题,您不能将一大块IL从一个程序集移植到另一个程序集,除非该目标程序集具有相同的元数据。或者除非您将IL中的元数据标记值替换为匹配的值目标程序集的元数据。这当然是非常不切实际的,你基本上最终会复制程序集。不妨复制一个程序集。或者就此而言,还不如使用原始程序集中的现有IL。您需要考虑一下,您对自己真正要实现的目标没有清晰的认识。System.CodeDom和Reflection.Emit命名空间可用于动态生成代码。如果我采用以下方法:publicstaticintMultiply(inta,intb){returna*b;在发布模式下编译并在代码中使用它,一切正常。如果我检查il数组,它包含4个字节,与Jon的答案中的四个指令(ldarg.0、ldarg.1、mul、ret)完全对应。如果我在调试模式下编译它,代码是9个字节。在Reflector中它看起来像这样:.methodpublichidebysigstaticint32Multiply(int32a,int32b)cilmanaged{.maxstack2.localsinit([0]int32CS$1$0000)L_0000:nopL_0001:ldarg.0L_0002:ldarg.1L_0003:mulL_0004:stloc.0L_0005:br.sL_0007L_0007:ldloc.0L_0008:ret}有问题的部分是局部变量。如果仅复制指令字节,则会发出一条使用局部变量的指令,但您永远不会声明它。如果您使用的是ILGenerator,则可以使用DeclareLocal()。似乎您可以在.Net4.5中使用MethodBuilder.SetMethodBody()设置局部变量(以下在VS11DP中对我有用):varmodule=typeof(Experiment).Module;mb.SetMethodBody(il,methodBody.MaxStackSize,module.ResolveSignature(methodBody.LocalSignatureMetadataToken),null,null);但是我没有找到在.Net4中执行此操作的方法,除了在调用CreateMethodBody()后使用反射来设置MethodBuilder的私有字段,该字段包含局部变量:typeof(MethodBuilder).GetField("m_localSignature",BindingFlags.NonPublic|BindingFlags.Instance).SetValue(mb,module.ResolveSignature(methodBody.LocalSignatureMetadataToken));关于原始错误:UsingtokenstoreferenceotherassembliessuchasSystem.ConsoleandSystem.Console.WriteLine)。这些令牌因程序集而异。这意味着如果您查看指令字节,那么在一个程序集中调用Console.WriteLine()的代码将不同于在另一个程序集中调用相同方法的代码。这意味着您必须真正理解IL字节的含义,并将引用类型、方法等的所有标记替换为您正在构建的程序集中有效的标记。我认为问题是在另一种类型/程序集中使用IL。如果你替换这个:mb.CreateMethodBody(il,il.Count());有了这个:ILGeneratorgenerator=mb.GetILGenerator();generator.Emit(OpCodes.Ldarg_0);generator.Emit(OpCodes.Ldarg_1);生成器.Emit(OpCodes.Mul);generator.Emit(OpCodes.Ret);然后它将正确执行该方法(没有Console.WriteLine,但它返回正确的值)。如果您真的需要能够从现有方法中剥离IL,那么您需要进一步挖掘——但如果您只需要验证其余代码是否正常工作,这可能会有所帮助。您可能感兴趣的一件事是,如果您从实验中取出Console.WriteLine调用,原始代码中的错误会发生变化。它变成了InvalidProgramException。我不知道为什么......如果我很好地理解你的问题,而你只是想动态生成一些.NET代码,并在远程客户端上执行它,那么就看看IronPython。您只需使用脚本创建字符串,然后将其发送给客户端,客户端就可以在运行时执行它以访问所有.NETFramework,甚至在运行时干扰您的客户端应用程序。很难让“复制”方法起作用,这需要一段时间。查看ILSpy,这是一个用于查看和分析现有代码的开源应用程序。您可以从用于分析IL-ASM代码的项目中提取代码,并使用它来重现该方法。这个答案有点正交——更多的是关于问题而不是所涉及的技术。您可以使用表达式树-它们很好用并且具有VS语法糖。对于序列化,你需要这个库(你也需要编译它):http://expressiontree.codeplex.com/(这显然适用于Silverlight4。)表达式树是有限的,因为它们只支持Lambda表达式-即没有块。这并不是真正的限制,因为您仍然可以在lambda中定义其他lambda方法,并将它们传递给函数式编程助手,例如LinqAPI。我包含了一个简单的扩展方法来向您展示如何向该函数添加其他辅助方法——关键是您需要在序列化程序中包含包含扩展的程序集。这段代码有效:以上是C#学习教程:动态创建一个方法并执行它共享的所有内容。如果对大家有用,需要进一步了解C#学习教程,希望大家多多关注——usingSystem;使用System.Linq;使用System.Linq.Expressions;使用表达式序列化;使用System.Xml.Linq;使用System.Collections.Generic;使用System.Reflection;namespaceExpressionSerializationTest{publicstaticclassFunctionalExtensions{publicstaticIEnumerableto(thisintstart,intend){for(;start>sumRange=(x,y)=>x.to(y).Sum();staticvoidMain(string[]args){conststringfileName="sumRange.bin";ExpressionSerializerserializer=newExpressionSerializer(newTypeResolver(new[]{Assembly.GetExecutingAssembly()}));serializer.Serialize(sumRange).Save(fileName);表达式>deserializedSumRange=serializer.Deserialize>(XElement.Load(fileName));FuncfuncSumRange=deserializedSumRange.Compile();Console.WriteLine("Deserializedfuncreturned:{0}",funcSumRange(1,4));控制台。读取密钥();}}}本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如有转载请注明出处:
