Search code examples
c#reflection.emitbltoolkitilasmilgenerator

Common Language Runtime detected an invalid program - ILGenerator


Basically i am trying to deserialize data that is inside an byte array into objects. I am trying to use GetString method of UTF8 Encoding in order to read a string. Here is part of my code:

var mm = new DynamicMethod("get_value", typeof(object)
                         , new Type[] { typeof(byte[]), typeof(int), typeof(int), typeof(int) });

        var utf8 = Encoding.GetEncoding("utf-8"); //l.GetValue(null, null).GetType().GetMethod("GetString");

        var getstring = utf8.GetType().GetMethod("GetString", new Type[] { typeof(byte[]), typeof(int), typeof(int) });
       // var getString = Encoding.UTF8.GetType().GetMethod("GetString", new Type[] { typeof(byte[]), typeof(int), typeof(int) });

        var h = new EmitHelper(mm.GetILGenerator());

        var defaultCase = h.ILGenerator.DefineLabel();

        var lbs = new Label[] { h.DefineLabel(),h.DefineLabel()};

        var getInt32Method = typeof(BitConverter).GetMethod("ToInt32", new Type[] { typeof(byte[]), typeof(int) });

        //Arg0 = Byte [] , Arg1 = type, Arg2 = size, Arg3 = offset
        h
            .ldarg_1
            .@switch(lbs)
            .MarkLabel(defaultCase)
            .ldnull
            .ret();

        h
            .MarkLabel(lbs[0])
            .ldarg_0 
            .ldarg_3
            .call(getInt32Method)
            .box(typeof(int))
            .ret();

        //THis is the function that is causing problem; If i remove this function works;
        h 
            .MarkLabel(lbs[1])
            .ldarg_0 //Load array 
            .ldarg_3 //Load Offset (index)
            .ldarg_2 //Load Size (count)
            .callvirt(getstring)
            .boxIfValueType(typeof(string))
            .ret();

        return (GetValueDelegate)mm.CreateDelegate(typeof(GetValueDelegate));

I checked the method signature of 'getstring' outside of this code and it works. I tried removing '.boxIfAny' and i also tried using 'call' rather then 'callvirt'. As I understand 'callvirt' is for instance methods which applies in this case. Any Ideas?


Solution

  • Calling the GetString method requires an instance.

    I've simplified your code and made it into a SSCCE:

    using System;
    using System.Reflection.Emit;
    using System.Text;
    
    class GetStringDemo {
        public static DynamicMethod GetStringForEncoding(Encoding encoding) {
    
            var getstringMethod = encoding.GetType().GetMethod("GetString", 
                new Type[] { typeof(byte[]) });    
            var getStringCreator = new DynamicMethod("foo", typeof(string), 
                new Type[] { typeof(byte[]), encoding.GetType() }, typeof(void));
            ILGenerator gen = getStringCreator.GetILGenerator();
    
            gen.Emit(OpCodes.Ldarg_1);  // this is the instance for callvirt
            gen.Emit(OpCodes.Ldarg_0);        
            gen.Emit(OpCodes.Callvirt, getstringMethod);
            gen.Emit(OpCodes.Box, typeof(string));
            gen.Emit(OpCodes.Ret);
    
            return getStringCreator;
        }
    
        public static void Main() {
    
            var utf8 = Encoding.GetEncoding("utf-8");
            var method = GetStringForEncoding(utf8);
            Console.WriteLine(method.Invoke(null, new object[2] { 
                new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20,
                             0x32, 0x30, 0x31, 0x34, 0x21 }, 
                utf8 } ));
        }
    }
    // Output:
    Hello 2014!
    

    Load the actual invocation target before you call h.ldarg_0 //Load array. In its absence you'll indeed get System.InvalidProgramException thrown by mscorlib.