Ricardo Barbosa
Ricardo Barbosa

Reputation: 123

OpCode.Call to Generic Method in different assembly using raw IL

I want to call a generic method using raw IL instructions. In order to learn how to do that I am using Reflection.Emit and experimenting with a dynamic assembly.

The method which I want to make a call is the following:

public class Class1
{
    public void Method1<T>()
    {
        Console.WriteLine("ClassLibrary1.Class1.Method1<T>()");
    }
}

These are the instructions I am using

 byte[] ilCodes = new byte[7];
        ilCodes[0] = (byte)OpCodes.Ldarg_0.Value;
        ilCodes[1] = (byte)OpCodes.Call.Value;
        ilCodes[2] = (byte)(token & 0xFF);
        ilCodes[3] = (byte)(token >> 8 & 0xFF);
        ilCodes[4] = (byte)(token >> 16 & 0xFF);
        ilCodes[5] = (byte)(token >> 24 & 0xFF);
        ilCodes[6] = (byte)0x2A;

This method resides in a diferent assembly and the token you see up there is obtained like this:

int token = moduleBuilder.GetMethodToken(typeof(ClassLibrary1.Class1).GetMethod("Method1").GetGenericMethodDefinition()).Token;

I am setting the bytes with MethodBuilder.CreateMethodBody method. Now after I set the method body with those bytes, create the assembly and call its method, it fails. When I inspect the generated code in Reflector it shows me that the method call is lacking the generic parameter

    .method public hidebysig instance void Method1<T>() cil managed {
.maxstack 1
L_0000: ldarg.0 
L_0001: call instance void [ClassLibrary1]ClassLibrary1.Class1::Method1() <-- this should contain Method1<!!T>()
L_0006: ret }

How can I generate that parameter reference in order for this to work? I know how to do it with ILGenerator but to this project its mandatory to do it with raw instructions.

Thank you.

EDIT: This is what ILDASM shows me (as @Lasse suggested)

.method /*06000001*/ public hidebysig instance void 
        Method1<T>() cil managed
// SIG: 30 01 00 01
{
  // Method begins at RVA 0x2050
  // Code size       7 (0x7)
  .maxstack  1
  IL_0000:  /* 02   |                  */ ldarg.0
  IL_0001:  /* 28   | (2B)000001       */ call       instance void [ClassLibrary1/*23000002*/]ClassLibrary1.Class1/*01000002*/::Method1<!!0>() /* 2B000001 */
  IL_0006:  /* 2A   |                  */ ret
} // end of method Class1::Method1

Upvotes: 3

Views: 1150

Answers (2)

svick
svick

Reputation: 244777

Since ILGenerator can do this, I looked at what it does. What I figured out is that you need a MethodSpec token, but to get that, you will need to call several internal methods, just like the above linked code (I used ExposedObject to make some of the reflection simpler):

int token = moduleBuilder.GetMethodToken(typeof(Class1).GetMethod("Method1")).Token;

SignatureHelper sigHelper = Exposed.From(typeof(SignatureHelper))
    .GetMethodSpecSigHelper(moduleBuilder, new Type[] { typeParameter });

var getSignatureParameters = new object[] { 0 };
byte[] bytes = (byte[])typeof(SignatureHelper).GetMethod(
    "InternalGetSignature", BindingFlags.NonPublic | BindingFlags.Instance)
    .Invoke(sigHelper, getSignatureParameters);

int length = (int)getSignatureParameters[0];

var runtimeModule = Exposed.From(moduleBuilder).GetNativeHandle();

token = (int)typeof(TypeBuilder)
    .GetMethod("DefineMethodSpec", BindingFlags.NonPublic | BindingFlags.Static)
    .Invoke(null, new object[] { runtimeModule, token, bytes, length });

Here, typeParameter is the GenericTypeParameterBuilder returned from DefineGenericParameters. Using this code, Reflector shows the following IL, which I believe is what you wanted (except that I made Method1 static and so replaced ldarg.0 with nop):

.method privatescope static void M2<U>() cil managed
{
    .maxstack 16
    L_0000: nop 
    L_0001: call void [ClassLibrary1]Class1::Method1<!!U>()
    L_0006: ret 
}

Upvotes: 4

atlaste
atlaste

Reputation: 31116

First off, be sure that you use the correct call or callvirt. It's usually best to use callvirt if you have an reference type, because it will also implicitly add a null check - but apart from that, if you're using any type inheritance, your PEVerify will fail and it will therefore result in undefined behavior.

Next, I'd have to add that finding the correct method to call is usually pretty nasty.

You probably want to avoid using MakeGenericMethod because that means you also need to use GetMethod, which might give you the incorrect overload. Implementing your own overload resolution rules that reflect .NET is possible, but pretty difficult (I have a post on SO somewhere about that that includes the solution).

The easiest way to go is to make generic types and call methods. Basically you can do that like this:

Type myType = typeof(MyType<>).MakeGenericType(someType);

That will give you a wrapper containing a generic type for the TypeBuilder you're using. (Assuming someType is a TypeBuilder if I understand your question correctly).

Next, you need to get the correct method. Again, this is nasty, since the wrapper doesn't have the right methods yet -- after all, we're still building the type. TypeBuilder has a static method for that, GetMethod that allows you to use the method in the generic definition to lookup the method in your newly brew generic thing. For example:

var baseMethod = 
    TypeBuilder.GetMethod(typeof(IEquatable<>).MakeGenericType(builder)), 
    typeof(IEquatable<>).GetMethod("Equals" /* add constraints */));

Two things to note when you go down this path:

  1. Save your assemblies to disk, and use PEVerify for everything. Even if it runs! It'll save you a ton of trouble.
  2. You might want to consider simply casting and using object or a normal non-generic interface. As long as you're not using value types, the IL will only JIT the code once, so there is usually no performance penalty. (If you're interested, I have a question about this on SO)

Upvotes: 2

Related Questions