OpCode.Call for shared method in different assemblies using raw IL

I want to call a generic method using raw IL commands. To see how to do this, I am using Reflection.Emit and experimenting with dynamic assembly.

The method I want to do 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 is in another assembly, and the token you see is obtained like this:

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

      

I am setting bytes using the MethodBuilder.CreateMethodBody method . Now, after I have set the method body with these bytes, create an assembly and call its method, it fails. When I check the generated code in Reflector it shows me that there is no common parameter in the method call

    .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 this parameter link to make it work? I know how to do it using ILGenerator , but for this project it needs to be done with raw instructions.

Thank.

EDIT: This 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

      

+3


source to share


2 answers


So how ILGenerator

can it do it, I looked at what it does . I figured you need a token MethodSpec

, but for that you will need to call several internal methods, just like the above linked code (I used ExposedObject to make some of the reflections easier):

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 it GenericTypeParameterBuilder

, returned from DefineGenericParameters

. Using this code, Reflector renders the following IL, which I believe is what you wanted (except that I made it Method1

static and therefore replaced it 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 
}

      

+4


source


First of all, make sure you are using the correct call

or callvirt

. It's usually better to use callvirt

if you have a reference type, because it will also implicitly add a null check - but beyond that, if you use inheritance of any type, your PEVerify will fail and this will result in undefined.

Next, I must add that finding the correct method to call is usually pretty frustrating.

You probably want to avoid using MakeGenericMethod

, because that means you need to use as well GetMethod

, which could lead to incorrect overloading. Implementing your own overload resolution rules that reflect .NET is possible, but rather difficult (I have a post on SO somewhere on what the solution includes).

The easiest way is to create generic types and call methods. Basically you can do it like this:

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

      



This will give you a wrapper containing the generic type for the TypeBuilder you are using. (Assuming which someType

is the TypeBuilder if I understand your question correctly).

Then you need to get the correct method. Again, this is frustrating as the wrapper doesn't have the correct methods yet - after all, we're still building the type. TypeBuilder has a static method for this, GetMethod

which allows you to use a method in a generic definition to find a method in your new brew of a generic thing. For example:

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

      

Two things to consider as you go this route:

  • Save your assemblies to disk and use PEVerify for everything. Even if it works! This will save you a ton of trouble.
  • You might want to consider a simple use and use, object

    or a common non-generic interface. As long as you don't use value types, IL will only be JIT code once, so there is usually no performance penalty. (If you're interested, I have a question about this on SO)
+2


source







All Articles