简体   繁体   English

IL使用Reflection.Emit调用params object []参数的方法

[英]IL Calling a method with params object[] arguments using Reflection.Emit

I'm writing a library that requires a later type build. 我正在编写一个需要后续类型构建的库。 Library uses platform .Net core 2.0 库使用平台.Net core 2.0

There is issue with some type that I am generating using Reflection.Emit 我使用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);
    }
}

for these types: 对于这些类型:

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)
    {
    }
}

Sample IL code: 样本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);
    }
}

Whenever I try to fill out a comment "ToDo", I get an error "Common Language Runtime detected an invalid program". 每当我尝试填写评论“ToDo”时,我都会收到错误“Common Language Runtime检测到无效程序”。

I ask to help with a call of method DoClass. 我请求帮助调用方法DoClass。

Thanks 谢谢

To call DoClass method you need to provide arguments, just Call classMethod isn't going to work. 要调用DoClass方法,您需要提供参数,只需Call classMethod就行了。

First argument is of course "this" reference: 第一个参数当然是“这个”参考:

genIM.Emit(OpCodes.Ldarg_0);

Second argument is array of objects. 第二个参数是对象数组。 params is compiler feature, if you build code yourself - you have to treat it as if params is not there. params是编译器功能,如果您自己构建代码 - 您必须将其视为params不在那里。 What I mean is - while 我的意思是 - 同时

 DoClass();

is legal when you write it in code - this is compiled as: 在代码中编写它是合法的 - 这被编译为:

DoClass(new object[0]);

So when you are emitting this call - you should always provide array of objects argument, you cannot omit it. 因此,当您发出此调用时 - 您应该始终提供对象数组参数,您不能省略它。

To push array of objects to stack: 要将对象数组推送到堆栈:

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

At this point your code will compile and run fine. 此时您的代码将编译并运行正常。 This is analog of: 这类似于:

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

If you want to pass all arguments of DoInterface , that requires more work. 如果要传递DoInterface所有参数, DoInterface需要更多工作。 I'll provide couple examples. 我将提供几个例子。 To pass first argument ( string arg0 ): 传递第一个参数( 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);

To pass second argument: 传递第二个参数:

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

And so on. 等等。

When you will push arguments into your array, don't forget to change its length to reflect number of arguments: 当您将参数推入数组时,不要忘记更改其长度以反映参数的数量:

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

Note also that you might want to change: 另请注意,您可能想要更改:

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

to

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

Or just change to baseType = typeof(A) , because you want to inherit your generated class from A , not from object . 或者只是更改为baseType = typeof(A) ,因为您要从A继承生成的类,而不是从object继承。

Full code to emit call with first 2 args: 使用前2个args发出调用的完整代码:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM