Search code examples
c#reflectionfuncreflection.emitdynamicmethod

C# Reflection Emit Call with parameter


I want to call a function with a parameter with the reflection.emit API. In the following is what I have at the moment. But when I run it, it throws the following exception: System.InvalidProgramException : Common Language Runtime detected an invalid program. So my question is what I have wrong in my code snippet below? Can someone help me out?

public class Test
{

    public void test()
    {
        Func<int, long> realSquareFunc = (val) => val * val;
        Type[] methodArgs = { typeof(int) };

        DynamicMethod squareIt = new DynamicMethod(
            "SquareIt",
            typeof(long),
            methodArgs,
            typeof(Test).Module)
        ;

        ILGenerator il = squareIt.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); // Save parameter on stack
        il.Emit(OpCodes.Call, realSquareFunc.Method); // Call function with input as parameter
        il.Emit(OpCodes.Ret); // Return value from function call before

        var myMethod = (Func<int, long>)squareIt.CreateDelegate(realSquareFunc.GetType());
        var result = myMethod.Invoke(4); // Should be 16 (4*4)
    }

}

Solution

  • Your code would work as is if the called method was a static method:

    public static long RealSquare(int val) => val * val;
    
    public void test()
    {
        Func<int, long> realSquareFunc = RealSquare;
        // ...
    

    However, the realSquareFunc = (val) => val * val lambda is actually compiled as an instance method of a hidden class. To call an instance method, the instance must be pushed onto the stack first, before the method arguments. Instance method calls also typically use the Callvirt opcode (regardless of whether they're virtual, because this opcode does null reference checking):

    public class Test
    {
        public long RealSquare(int val) => val * val;
    
        public void test()
        {
            Func<int, long> realSquareFunc = RealSquare;
            // pass the instance we want to call the method on in as well
            Type[] methodArgs = { typeof(Test), typeof(int) };
    
            DynamicMethod squareIt = new DynamicMethod(
                "SquareIt",
                typeof(long),
                methodArgs,
                typeof(Test).Module)
            ;
    
            ILGenerator il = squareIt.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0); // Push the target instance onto stack      
            il.Emit(OpCodes.Ldarg_1); // Save parameter on stack
            il.Emit(OpCodes.Callvirt, realSquareFunc.Method); // Call function with input as parameter
            il.Emit(OpCodes.Ret); // Return value from function call before
    
            var myMethod = (Func<Test, int, long>)squareIt.CreateDelegate(typeof(Func<Test, int, long>));
            var result = myMethod.Invoke(this, 4); // Should be 16 (4*4)
        }  
    }
    

    Calling the target method of a lambda directly is more complicated due to the compiler-generated class involved, but if you want to call a delegate in general it works like this:

    public class Test
    {        
        public void test()
        {
            Func<int, long> realSquareFunc = (val) => val * val;
            // pass the delegate we want to call into the method
            Type[] methodArgs = { realSquareFunc.GetType(), typeof(int) };
    
            DynamicMethod squareIt = new DynamicMethod(
                "SquareIt",
                typeof(long),
                methodArgs,
                typeof(Test).Module)
            ;
    
            ILGenerator il = squareIt.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0); // Push the delegate onto stack        
            il.Emit(OpCodes.Ldarg_1); // Save parameter on stack
            il.Emit(OpCodes.Callvirt, realSquareFunc.GetType().GetMethod("Invoke")); // Invoke delegate
            il.Emit(OpCodes.Ret); // Return value from function call before
    
            var myMethod = (Func<Func<int, long>, int, long>)squareIt
                .CreateDelegate(typeof(Func<Func<int, long>, int, long>));
            var result = myMethod.Invoke(realSquareFunc, 4); // Should be 16 (4*4)
        }  
    }