Search code examples
c#reflectionreflection.emitdynamicmethod

C# - Reflection.Emit : Return result of called method


In a DynamicMethod I try to call a method that wants an array of objects to return the length of the given array. Currently, my method which should be called from the DynamicMethod looks like the following:

public static int Test(Object[] args)
{
    Console.WriteLine(args.Length);
    return args.Length;
}

The creation process of the DynamicMethod looks like the following:

(The creation of the Object array is adopted from the following SO answer)

public static DynamicMethod GetDM()
{ 
    var returnType = typeof(int);
    var paramTypes = new Type[]{typeof(string), typeof(bool)};

    var method = new DynamicMethod(
        "",
        returnType,
        paramTypes,
        false
    );
    var il = method.GetILGenerator();

    // Save parameters in an object array
    il.Emit(OpCodes.Ldc_I4_S, paramTypes.Length);
    il.Emit(OpCodes.Newarr, typeof(Object));
    il.Emit(OpCodes.Dup);

    for (int i = 0; i < paramTypes.Length; i++)
    {
        Type type = paramTypes[i];

        il.Emit(OpCodes.Ldc_I4, i);
        il.Emit(OpCodes.Ldarg, i);
        if (type.IsValueType) { il.Emit(OpCodes.Box, type); }
        il.Emit(OpCodes.Stelem_Ref);
        il.Emit(OpCodes.Dup);
    }

    // Call method and get the length of the array
    // How do I return the result of the called method?
    var callMethod = typeof(Program).GetMethod("Test", (BindingFlags)(-1));
    il.Emit(OpCodes.Call, callMethod);

    il.Emit(OpCodes.Ret);

    return method;
}

With the following method I check the functionality:

public static void Main(string[] args)
{
    var method = GetDM();
    var result = method.Invoke(null, new Object[]{"Test 1234", true});
    Console.WriteLine(result); // Should be 2
}

When I run the main method I get the System.Reflection.TargetInvocationException. Can someone help me out how to return the value which was returned by the called method? Here is a link to a dotnetfiddle to see my problem in action.


Solution

  • After the for loop you have the constructed object array twice on the stack (because of the Dup calls). Only one of these array references is consumed by the Call so at the end of the method you will have one extra array reference on the stack.

    To correct this, remove the first Dup and move the second to the head of the loop body:

    public static DynamicMethod GetDM() {  
        var returnType = typeof(int);
        var paramTypes = new Type[]{typeof(string), typeof(bool)};
    
        var method = new DynamicMethod(
            "",
            returnType,
            paramTypes,
            false
        );
        var il = method.GetILGenerator();
    
        // Save parameters in an object array
        il.Emit(OpCodes.Ldc_I4_S, paramTypes.Length);
        il.Emit(OpCodes.Newarr, typeof(Object));
    
        for (int i = 0; i < paramTypes.Length; i++)
        {
            Type type = paramTypes[i];
    
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Ldc_I4, i);
            il.Emit(OpCodes.Ldarg, i);
            if (type.IsValueType) { il.Emit(OpCodes.Box, type); }
            il.Emit(OpCodes.Stelem_Ref);
        }
    
        // Call method and get the length of the array
        // How do I return the result of the called method?
        var callMethod = typeof(Program).GetMethod("Test", (BindingFlags)(-1));
        il.Emit(OpCodes.Call, callMethod);
    
        il.Emit(OpCodes.Ret);
    
        return method;
    }