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
source to share
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
}
source to share
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)
source to share