代码之家  ›  专栏  ›  技术社区  ›  popelenkow

IL使用反射调用具有params object[]参数的方法。发出

  •  4
  • popelenkow  · 技术社区  · 7 年前

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

    我使用反射生成的某些类型存在问题。发出

    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”时,都会出现错误“公共语言运行库检测到无效程序”。

    我请求帮助调用方法DoClass。

    谢谢

    1 回复  |  直到 7 年前
        1
  •  9
  •   Evk    7 年前

    调用 DoClass 方法,您需要提供参数 Call classMethod 不会起作用的。

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

    genIM.Emit(OpCodes.Ldarg_0);
    

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

     DoClass();
    

    用代码编写时是合法的-编译如下:

    DoClass(new object[0]);
    

    所以,当您发出这个调用时,您应该始终提供array of objects参数,不能忽略它。

    要将对象数组推送到堆栈,请执行以下操作:

    // 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);
    

    等等

    将参数推入数组时,不要忘记更改其长度以反映参数的数量:

    // 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);