c# - IL 使用 Reflection.Emit 调用带有 params object[] 参数的方法

标签 c# cil reflection.emit

我正在编写一个需要稍后类型构建的库。库使用平台.Net core 2.0

我使用 Reflection.Emit 生成的某些类型存在问题

public class GeneratedA : A, IA
{
    public void DoInterface(string arg0, bool arg1, int arg2, object arg3, List<float> arg4, params object[] otherArgs)
    {
        DoClass(arg0, arg1, arg2, arg3, arg4, otherArgs);
    }
}

对于这些类型:

public interface IA
{
    void DoInterface(string arg0, bool arg1, int arg2, object arg3, List<float> arg4,  params object[] otherArgs);
}
public class A
{
    public void DoClass(params object[] args)
    {
    }
}

示例 IL 代码:

class Program
{
    public static class Generator
    {
        public static T Create<T>()
            where T : class
        {
            AssemblyName aName = new AssemblyName("DynamicAssembly");
            AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.Run);
            ModuleBuilder mb = ab.DefineDynamicModule(aName.Name);

            var interfaceType = typeof(T);
            var interfaceMethod = interfaceType.GetMethod("DoInterface");
            var interfaceMethodArgs = interfaceMethod.GetParameters().Select(x => x.ParameterType).ToArray();
            var classType = typeof(A);
            var classMethod = classType.GetMethod("DoClass");
            var returnType = typeof(void);
            var baseType = typeof(object);
            var baseConstructor = baseType.GetConstructor(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance, null, Type.EmptyTypes, null);


            TypeBuilder tb = mb.DefineType("GeneratedA", TypeAttributes.Public, baseType);


            ConstructorBuilder ctor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
            ILGenerator ctorIL = ctor.GetILGenerator();
            ctorIL.Emit(OpCodes.Ldarg_0);
            ctorIL.Emit(OpCodes.Call, baseConstructor);
            ctorIL.Emit(OpCodes.Nop);
            ctorIL.Emit(OpCodes.Nop);
            ctorIL.Emit(OpCodes.Ret);


            tb.AddInterfaceImplementation(interfaceType);

            MethodBuilder mbIM = tb.DefineMethod(interfaceType.Name + "." + interfaceMethod.Name,
                MethodAttributes.Private | MethodAttributes.HideBySig |
                MethodAttributes.NewSlot | MethodAttributes.Virtual |
                MethodAttributes.Final,
                returnType,
                interfaceMethodArgs);
            ILGenerator genIM = mbIM.GetILGenerator();
            // ToDo
            genIM.Emit(OpCodes.Call, classMethod);
            genIM.Emit(OpCodes.Ret);


            tb.DefineMethodOverride(mbIM, interfaceMethod);

            Type t = tb.CreateType();

            return Activator.CreateInstance(t) as T;
        }

    }

    static void Main(string[] args)
    {
        IA a;
        a = new GeneratedA();
        a.DoInterface("0", true, 2, 3, new List<float>() { 4 }, "5", 6);
        a = Generator.Create<IA>();
        a.DoInterface("0", true, 2, 3, new List<float>() { 4 }, "5", 6);
    }
}

每当我尝试填写评论“ToDo”时,我都会收到错误“Common Language Runtime detected an invalid program”。

我请求帮助调用方法 DoClass。

谢谢

最佳答案

要调用 DoClass 方法,您需要提供参数,只是 Call classMethod 是行不通的。

第一个参数当然是“this”引用:

genIM.Emit(OpCodes.Ldarg_0);

第二个参数是对象数组。 params 是编译器功能,如果您自己构建代码 - 您必须将其视为 params 不存在。我的意思是——同时

 DoClass();

用代码编写时是合法的——编译为:

DoClass(new object[0]);

所以当你发出这个调用时——你应该总是提供对象数组参数,你不能省略它。

将对象数组压入堆栈:

// push array length (0, for example) to stack
genIM.Emit(OpCodes.Ldc_I4_0); 
// push new array with length given by the above value (0)
genIM.Emit(OpCodes.Newarr, typeof(object)); 

此时您的代码将编译并正常运行。这是模拟:

public class GeneratedA : A, IA
{
    public void DoInterface(string arg0, bool arg1, int arg2, object arg3, List<float> arg4, params object[] otherArgs)
    {
        DoClass();
    }
}

如果你想传递 DoInterface 的所有参数,那需要做更多的工作。我将提供几个例子。传递第一个参数(string arg0):

genIM.Emit(OpCodes.Dup);
// push index to store next element at (0)
genIM.Emit(OpCodes.Ldc_I4_0);
// push first argument (arg0 of DoInterface) to stack
genIM.Emit(OpCodes.Ldarg_1);
// store element in array at given index (yourArguments[0] = arg0)
genIM.Emit(OpCodes.Stelem_Ref);

传递第二个参数:

genIM.Emit(OpCodes.Dup);
// push index to store next element at (1)
genIM.Emit(OpCodes.Ldc_I4_1);
// push arg2
genIM.Emit(OpCodes.Ldarg_2);
// box, because boolean is value type, and you store it in object array
genIM.Emit(OpCodes.Box, typeof(bool));
// store in array (yourArguments[1] = (object) arg2
genIM.Emit(OpCodes.Stelem_Ref);

等等。

当你将参数压入你的数组时,不要忘记改变它的长度以反射(reflect)参数的数量:

// push array length - 6
genIM.Emit(OpCodes.Ldc_I4_6); 
// push new array with length given by the above value (6)
genIM.Emit(OpCodes.Newarr, typeof(object)); 

另请注意,您可能想要更改:

TypeBuilder tb = mb.DefineType("GeneratedA", TypeAttributes.Public, baseType);

TypeBuilder tb = mb.DefineType("GeneratedA", TypeAttributes.Public, classType);

或者只是更改为 baseType = typeof(A),因为您希望从 A 继承生成的类,而不是从 object

使用前 2 个参数发出调用的完整代码:

ILGenerator genIM = mbIM.GetILGenerator();            
genIM.Emit(OpCodes.Ldarg_0);   
genIM.Emit(OpCodes.Ldc_I4_2);
genIM.Emit(OpCodes.Newarr, typeof(object));

genIM.Emit(OpCodes.Dup);
genIM.Emit(OpCodes.Ldc_I4_0);
genIM.Emit(OpCodes.Ldarg_1);
genIM.Emit(OpCodes.Stelem_Ref);

genIM.Emit(OpCodes.Dup);
genIM.Emit(OpCodes.Ldc_I4_1);
genIM.Emit(OpCodes.Ldarg_2);
genIM.Emit(OpCodes.Box, typeof(bool));
genIM.Emit(OpCodes.Stelem_Ref);

genIM.Emit(OpCodes.Call, classMethod);
genIM.Emit(OpCodes.Ret);

关于c# - IL 使用 Reflection.Emit 调用带有 params object[] 参数的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48965617/

相关文章:

c# - 如何将 IL 翻译成 Emit 语句?

c# - 支持自定义数字类型的 ToString(字符串格式)

C# 泛型继承数据访问

c# - 有没有办法在 C# 中抑制类似于 Java 的 @SuppressWarnings 注释的警告?

c# - 使用 MonoTouch 创建自定义图像过滤器

compiler-construction - 从哪里获得 F# ILX2CIL 汇编程序?

c++-cli - VS2015 C++/CLI Release Build 中的 NullReferenceException

c# - 为什么对运算符 'op_Addition' 使用名称 '+' 而不是名称 '+' ?

c# - 不一致的 Reflection.Emit 支持 Mono?

c# - 为什么在发出通过值类型泛型相互引用的类时会出现此异常?