C# 动态创建方法并执行它

C# 动态创建方法并执行它,c#,reflection,cil,reflection.emit,il,C#,Reflection,Cil,Reflection.emit,Il,背景: 我想在C#中定义几个静态方法,并从这些方法中的一个生成IL代码作为字节数组,在运行时(在客户端上)选择,然后通过网络将字节数组发送到另一台机器(服务器),在从字节数组重新生成IL代码后,应该在那里执行 我的尝试:() 然后我得到方法体的IL代码,如下所示: BindingFlags flags = BindingFlags.Public | BindingFlags.Static; MethodInfo meth = typeof(Experiment).GetMethod("Multi

背景:

我想在C#中定义几个
静态
方法,并从这些方法中的一个生成IL代码作为字节数组,在运行时(在客户端上)选择,然后通过网络将字节数组发送到另一台机器(服务器),在从字节数组重新生成IL代码后,应该在那里执行

我的尝试:()

然后我得到方法体的IL代码,如下所示:

BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
MethodInfo meth = typeof(Experiment).GetMethod("Multiply", flags);
byte[] il = meth.GetMethodBody().GetILAsByteArray();
到目前为止,我没有动态创建任何内容。但我将IL代码作为字节数组,我想创建一个程序集,然后是其中的一个模块,然后是一个类型,然后是一个方法——所有这些都是动态创建的。在创建动态创建的方法的方法体时,我使用在上面的代码中使用反射得到的IL代码

代码生成代码如下所示:

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());
}
但是它没有在输出窗口上打印
98780
,而是抛出一个异常:

System.Reflection.TargetInvocationException:调用的目标已引发异常。-->System.TypeLoadException:无法从程序集“MyDLL,版本=0.0.0.0,区域性=中性,PublicKeyToken=null”加载类型“无效的\u令牌.0x0100001E”。
在MyType.MyMethod中(Int32值1、Int32值2) [……]


请帮助我找出错误的原因,以及如何修复它。

我认为问题在于在另一种类型/程序集中使用IL。如果替换此项:

mb.CreateMethodBody(il, il.Count());
为此:

ILGenerator generator = mb.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Mul);
generator.Emit(OpCodes.Ret);
然后它将正确执行该方法(无
Console.WriteLine
,但返回正确的值)

如果您真的需要能够从现有方法中提取IL,那么您需要进一步研究——但是如果您只需要验证其余代码是否正常工作,这可能会有所帮助


您可能会发现有趣的一件事是,如果从
实验
中取出
控制台.WriteLine
调用,则原始代码中的错误会发生变化。它变成了一个
invalidProgrammeException
。如果我采用以下方法,我不知道为什么…

public static int Multiply(int a, int b)
{
    return a * b;
}
在发布模式下编译并在代码中使用它,一切正常。如果我检查
il
数组,它包含4个字节,与Jon答案中的四条指令(ldarg.0、ldarg.1、mul、ret)完全对应

如果我在调试模式下编译它,代码是9字节。在反射器中,它看起来像这样:

.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 
}
有问题的部分是局部变量。如果只是复制指令字节,则发出使用局部变量的指令,但从不声明它

如果使用的是
ILGenerator
,则可以使用
DeclareLocal()
。您似乎可以在.Net 4.5中设置局部变量(以下内容在VS 11 DP中适用):

但是我在.Net 4中没有找到这样做的方法,除了在调用
CreateMethodBody()
之后,使用反射设置
MethodBuilder
的私有字段,该字段包含局部变量:

关于原始错误:使用标记引用来自其他程序集(如
System.Console
System.Console.WriteLine
)的类型和方法。这些标记在不同的程序集中是不同的。这意味着在一个程序集中调用
Console.WriteLine()
的代码与在另一个程序集中调用相同方法的代码不同,如果您查看指令字节的话


这意味着您必须真正理解IL字节的含义,并将引用类型、方法等的所有标记替换为在您正在构建的程序集中有效的标记。

在任意程序集中运行ildasm.exe。使用View+Show标记值并查看一些反汇编代码。您将通过一个数字看到IL包含对其他方法和变量的引用。该编号是程序集元数据表的索引

也许您现在看到了问题所在,您无法将IL块从一个程序集移植到另一个程序集,除非该目标程序集具有相同的元数据。或者,除非将IL中的元数据标记值替换为与目标程序集的元数据匹配的值。这是非常不切实际的。当然,您最终会复制程序集。还不如复制一份程序集。或者,就这一点而言,也可以使用原始程序集中现有的IL


你需要仔细考虑一下,不清楚你到底想完成什么。System.CodeDom和Reflection.Emit名称空间可用于动态生成代码。

要使“复制”方法正常工作,有一种很难的方法,这需要一段时间


看看,这个应用程序是用来查看和分析现有代码的,并且是开源的。您可以从用于分析IL-ASM代码的项目中提取代码,并使用它复制该方法。

这个答案有点正交-更多的是关于问题,而不是涉及的技术

你可以使用表达式树——它们很好用,而且有语法上的糖分

对于序列化,您需要这个库(您也需要编译它): (显然,这也适用于Silverlight 4。)

表达式树的限制是它们只支持Lambda表达式,即不支持块

这并不是一个真正的限制,因为您仍然可以在lambda中定义其他lambda方法,并将它们传递给函数式编程助手,如linqapi

我已经包含了一个简单的扩展方法,向您展示了如何为函数性内容添加其他帮助器方法——关键是您需要在序列化程序中包含包含扩展的程序集

此代码适用于:

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();
        }
    }
}
使用系统;
使用System.Linq;
使用System.Linq.Expressions;
使用表达式序列化;
使用System.Xml.Linq;
使用System.Collections.Generic;
运用系统反思;
命名空间表达式序列化测试
{
var module = typeof(Experiment).Module;
mb.SetMethodBody(il, methodBody.MaxStackSize,
                 module.ResolveSignature(methodBody.LocalSignatureMetadataToken),
                 null, null);
typeof(MethodBuilder)
    .GetField("m_localSignature", BindingFlags.NonPublic | BindingFlags.Instance)
    .SetValue(mb, module.ResolveSignature(methodBody.LocalSignatureMetadataToken));
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();
        }
    }
}