Search code examples
c#.netreflection.emit

Why cannot I create a dynamic delegate which has more than one argument using .NET EMIT


There are some definations:

public class Message
{
    public SayType Say(string name)
    {
        Console.Write("Hello," + name );
        return SayType.Name;
    }
    
    public SayType Say(string name,string haha)
    {
        Console.Write("Hello," + name );
        return SayType.Name;
    }
}

public enum SayType
{
    Name
}

SayDelegate:

public delegate SayType SayDelegate(object message,params object[] o);

And I want to create two dynamic of the two function in class Message. The first:

DynamicMethod dynamicMethod = 
             new DynamicMethod("Say",typeof(SayType),new Type[]{typeof(object),typeof(object[])});
        var il = dynamicMethod.GetILGenerator();
        il.Emit(OpCodes.Ldarg,0); 
        il.Emit(OpCodes.Ldarg,1); 
        il.Emit(OpCodes.Ldc_I4,0);
        il.Emit(OpCodes.Ldelem_Ref);  
        il.Emit(OpCodes.Callvirt,typeof(Message).GetMethods()[0]);
        //[0] refers to the first method 
        il.Emit(OpCodes.Ret); 
        System.Delegate delegates = dynamicMethod.CreateDelegate(typeof(SayDelegate));
        delegates.DynamicInvoke(new Message(), new object[]{"123123"});

It can work normally.

However, if I create the second delegate like this:

DynamicMethod dynamicMethod = 
             new DynamicMethod("Say",typeof(SayType),new Type[]{typeof(object),typeof(object[])});
        var il = dynamicMethod.GetILGenerator();
        il.Emit(OpCodes.Ldarg,0); 
        il.Emit(OpCodes.Ldarg,1); 
        il.Emit(OpCodes.Ldc_I4,0);
        il.Emit(OpCodes.Ldelem_Ref); 
        il.Emit(OpCodes.Ldc_I4,1); 
        il.Emit(OpCodes.Ldelem_Ref); 
        il.Emit(OpCodes.Callvirt,typeof(Message).GetMethods()[1]);
        //[1] refers to the second method 
        il.Emit(OpCodes.Ret); 
        System.Delegate delegates = dynamicMethod.CreateDelegate(typeof(SayDelegate));
        delegates.DynamicInvoke(new Message(), new object[]{"123123","123123"});

It will have some complains:

 ---> System.InvalidProgramException: Common Language Runtime detected an invalid program.
   at Say(Object , Object[] )
   --- End of inner exception stack trace ---

Solution

  • You missed a ldarg.1:

    il.Emit(OpCodes.Ldarg, 0);
    il.Emit(OpCodes.Ldarg, 1);
    il.Emit(OpCodes.Ldc_I4, 0);
    il.Emit(OpCodes.Ldelem_Ref);
    il.Emit(OpCodes.Ldarg, 1); // <<== this one
    il.Emit(OpCodes.Ldc_I4, 1);
    il.Emit(OpCodes.Ldelem_Ref);
    

    The trick is to compile and decompile what you want, and look at the IL; in this case we see:

    IL_0000: ldarg.0
    IL_0001: castclass Message
    IL_0006: ldarg.1
    IL_0007: ldc.i4.0
    IL_0008: ldelem.ref
    IL_0009: castclass [System.Runtime]System.String
    IL_000e: ldarg.1
    IL_000f: ldc.i4.1
    IL_0010: ldelem.ref
    IL_0011: castclass [System.Runtime]System.String
    IL_0016: callvirt instance valuetype SayType Message::Say(string, string)
    IL_001b: ret
    

    and compare that to your opcodes (noting that you're using unsafe coerce, which ... I'm not going to advocate for, but I also can't stop you from doing)