Reputation: 123
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
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
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:
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