MAGx2
MAGx2

Reputation: 3189

Call other method in IlGenerator

I'm building my own type via TypeBuilder and I'm trying to add to this methods that will call methodInfo gathered from different object.

The problem is I don't know how to use ILGenerator.Emit or ILGenerator.EmitCall.

I've tried to use il.EmitCall(OpCodes.Call, methodInfo, arguments) and il.Emit(OpCodes.Call, methodInfo) but neither of them worked. Always I got this error:

[InvalidProgramException: Common Language Runtime detected an invalid program.]
   MyImplementationController.Hello1() +0

[TargetInvocationException: Exception has been thrown by the target of an invocation.]
   System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) +0
   System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) +192
   System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +155
   System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) +19

And this is my code:

        foreach (var methodInfo in methodInfosFromSomewhere)
        {
            var arguments = methodInfo.GetParameters().Select(a => a.ParameterType).ToArray();
            MethodBuilder mb = tb.DefineMethod(
                methodInfo.Name,
                MethodAttributes.Final | MethodAttributes.Public,
                CallingConventions.HasThis,
                methodInfo.ReturnType,
                arguments);

            // method 
            ILGenerator il = mb.GetILGenerator();
            int numParams = arguments.Length;
            for (byte x = 0; x < numParams; x++)
            {
                //il.Emit(OpCodes.Ldarg_S, x);
                il.Emit(OpCodes.Ldstr, x);
            }
            //il.EmitCall(OpCodes.Call, methodInfo, arguments);
            il.Emit(OpCodes.Call, methodInfo);

            il.Emit(OpCodes.Ret);
        }

@Edit

Finally I know (probably) where is problem! When I'm calling Emit.Call I don't want to call method in this object. I want to call method from another object.

Please look at this example:

// this is interface that we want to 'decorate'
public interface IMyInterface
{
    MyResponse Hello1();
    MyResponse Hello2(MyRequest request);
    MyResponse Hello3(MyRequest request);
}
public class MyImplementation : IMyInterface
{
    public MyResponse Hello1()
    {
        return new MyResponse { Name = "empty" };
    }
    // ... rest of implementation, it doesn't matter
}

The class that I want to generate is smthing like that:

public class GeneratedClass : ApiController
{
    public MyInterface myImplementation { get; set; }
    public MyResponse Hello1()
    {
        return myImplementation.Hello1();
    }
    // ... rest of implementation, it doesn't matter
}

As you can see I want to call method from other object. I know how to create property for object but I don't know how to call method from other object

Upvotes: 0

Views: 3822

Answers (1)

Mr Anderson
Mr Anderson

Reputation: 2233

From the source: (http://referencesource.microsoft.com/#mscorlib/system/reflection/emit/ilgenerator.cs,3e110f4a19d1c05e)

public virtual void Emit(OpCode opcode, MethodInfo meth)
{
    //...
    if (opcode.Equals(OpCodes.Call) || opcode.Equals(OpCodes.Callvirt) || opcode.Equals(OpCodes.Newobj))
    {
        EmitCall(opcode, meth, null);
    }
    else
    {
        // ...
    }
}

As you can see, Emit() calls EmitCall() if the OpCode is Call, Callvirt, or Newobj, so Emit() vs EmitCall() should not make a difference.

Emitting with OpCodes.Ldstr expects an operand of type string. What you want to do is load the arguments to the stack one by one before emitting the OpCodes.Call instruction.

Instead of:

for (byte x = 0; x < numParams; x++)
{
    il.Emit(OpCodes.Ldstr, x);
}

Try this:

switch (numParams)
{
    case 0:
        break;
    case 1:
        il.Emit(OpCodes.Ldarg_0);
        break;
    case 2:
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        break;
    case 3:
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldarg_2);
        break;
    default:
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldarg_2);
        il.Emit(OpCodes.Ldarg_3);
        for (int i = 4; i < numParams; i++)
        {
            il.Emit(OpCodes.Ldarg, mb.GetParameters()[i]);
        }
        break;
}

Edit after question update: You must define a property myImplementation in your new type.

Try this:

// Create field to back your "myImplementation" property
FieldBuilder newBackingField = tb.DefineField("backingField_myImplementation", typeof(MyInterface), System.Reflection.FieldAttributes.Private);
// Create your "myImplementation" property
PropertyBuilder newProp = tb.DefineProperty("myImplementation", System.Reflection.PropertyAttributes.None, typeof(MyInterface), Type.EmptyTypes);
// Create get-method for your property
MethodBuilder getter = tb.DefineMethod("get_myImplementation", System.Reflection.MethodAttributes.Private);
ILGenerator getterILGen = getter.GetILGenerator();
// Basic implementation (return backing field value)
getterILGen.Emit(OpCodes.Ldarg_0);
getterILGen.Emit(OpCodes.Ldfld, newBackingField);
getterILGen.Emit(OpCodes.Ret);

// Create set-method for your property
MethodBuilder setter = tb.DefineMethod("set_myImplementation", System.Reflection.MethodAttributes.Private);
setter.DefineParameter(1, System.Reflection.ParameterAttributes.None, "value");
ILGenerator setterILGen = setter.GetILGenerator();
// Basic implementation (set backing field)
setterILGen.Emit(OpCodes.Ldarg_0);
setterILGen.Emit(OpCodes.Ldarg_1);
setterILGen.Emit(OpCodes.Stfld, newBackingField);
setterILGen.Emit(OpCodes.Ret);

// Hello1 Method
MethodBuilder hello1 = tb.DefineMethod("Hello1", System.Reflection.MethodAttributes.Public);
ILGenerator il = hello1.GetILGenerator();

// Here, add code to load arguments, if any (as shown previously in answer)

il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, getter);
il.Emit(OpCodes.Callvirt, typeof(MyInterface).GetMethod("Hello1"));
il.Emit(OpCodes.Ret);

Upvotes: 1

Related Questions