OpCode.Call 到使用原始 IL 的不同程序集中的通用方法
OpCode.Call to Generic Method in different assembly using raw IL
我想使用原始 IL 指令调用通用方法。为了学习如何做到这一点,我正在使用 Reflection.Emit 并尝试动态装配。
我想调用的方法如下:
public class Class1
{
public void Method1<T>()
{
Console.WriteLine("ClassLibrary1.Class1.Method1<T>()");
}
}
这些是我正在使用的说明
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;
此方法驻留在不同的程序集中,您在上面看到的令牌是这样获得的:
int token = moduleBuilder.GetMethodToken(typeof(ClassLibrary1.Class1).GetMethod("Method1").GetGenericMethodDefinition()).Token;
我正在使用 MethodBuilder.CreateMethodBody 方法设置字节。
现在,在我用这些字节设置方法体、创建程序集并调用它的方法后,它失败了。当我在 Reflector 中检查生成的代码时,它告诉我方法调用缺少通用参数
.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 }
如何生成该参数引用才能使其正常工作?我知道如何使用 ILGenerator 来完成,但是对于这个项目来说,必须使用原始指令来完成。
谢谢。
编辑:这就是 ILDASM 向我展示的内容(正如@Lasse 建议的那样)
.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
首先,请确保使用正确的 call
或 callvirt
。如果您有引用类型,通常最好使用 callvirt
,因为它还会隐式添加空检查 - 但除此之外,如果您使用任何类型继承,您的 PEVerify 将失败,因此会导致在未定义的行为中。
接下来,我必须补充一点,找到正确的调用方法通常非常麻烦。
您可能希望避免使用 MakeGenericMethod
,因为这意味着您还需要使用 GetMethod
,这可能会给您带来不正确的重载。实施您自己的反映 .NET 的重载解析规则是可能的,但非常困难(我在 SO 上有一个包含解决方案的 post)。
最简单的方法是创建泛型类型和调用方法。基本上你可以这样做:
Type myType = typeof(MyType<>).MakeGenericType(someType);
这将为您提供一个包装器,其中包含您正在使用的 TypeBuilder 的通用类型。 (如果我正确理解你的问题,假设 someType
是一个 TypeBuilder )。
接下来,您需要获取正确的方法。同样,这很糟糕,因为包装器还没有正确的方法——毕竟,我们仍在构建类型。 TypeBuilder 有一个静态方法,GetMethod
允许您使用泛型定义中的方法来查找新 brew 泛型事物中的方法。例如:
var baseMethod =
TypeBuilder.GetMethod(typeof(IEquatable<>).MakeGenericType(builder)),
typeof(IEquatable<>).GetMethod("Equals" /* add constraints */));
走这条路时要注意两点:
- 将程序集保存到磁盘,并对所有内容使用 PEVerify。 即使它运行!它会为您省去很多麻烦。
- 您可能想考虑简单地转换和使用
object
或普通的非通用接口。只要您不使用值类型,IL 只会对代码进行一次 JIT,因此通常不会有性能损失。 (如果你有兴趣,我在 SO 上对此有疑问)
由于 ILGenerator
可以做到这一点,我查看了 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 以简化一些反射):
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 });
这里,typeParameter
是从DefineGenericParameters
返回的GenericTypeParameterBuilder
。使用此代码,Reflector 显示了以下 IL,我相信这是您想要的(除了我将 Method1
设为静态并因此将 ldarg.0
替换为 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
}
我想使用原始 IL 指令调用通用方法。为了学习如何做到这一点,我正在使用 Reflection.Emit 并尝试动态装配。
我想调用的方法如下:
public class Class1
{
public void Method1<T>()
{
Console.WriteLine("ClassLibrary1.Class1.Method1<T>()");
}
}
这些是我正在使用的说明
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;
此方法驻留在不同的程序集中,您在上面看到的令牌是这样获得的:
int token = moduleBuilder.GetMethodToken(typeof(ClassLibrary1.Class1).GetMethod("Method1").GetGenericMethodDefinition()).Token;
我正在使用 MethodBuilder.CreateMethodBody 方法设置字节。 现在,在我用这些字节设置方法体、创建程序集并调用它的方法后,它失败了。当我在 Reflector 中检查生成的代码时,它告诉我方法调用缺少通用参数
.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 }
如何生成该参数引用才能使其正常工作?我知道如何使用 ILGenerator 来完成,但是对于这个项目来说,必须使用原始指令来完成。
谢谢。
编辑:这就是 ILDASM 向我展示的内容(正如@Lasse 建议的那样)
.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
首先,请确保使用正确的 call
或 callvirt
。如果您有引用类型,通常最好使用 callvirt
,因为它还会隐式添加空检查 - 但除此之外,如果您使用任何类型继承,您的 PEVerify 将失败,因此会导致在未定义的行为中。
接下来,我必须补充一点,找到正确的调用方法通常非常麻烦。
您可能希望避免使用 MakeGenericMethod
,因为这意味着您还需要使用 GetMethod
,这可能会给您带来不正确的重载。实施您自己的反映 .NET 的重载解析规则是可能的,但非常困难(我在 SO 上有一个包含解决方案的 post)。
最简单的方法是创建泛型类型和调用方法。基本上你可以这样做:
Type myType = typeof(MyType<>).MakeGenericType(someType);
这将为您提供一个包装器,其中包含您正在使用的 TypeBuilder 的通用类型。 (如果我正确理解你的问题,假设 someType
是一个 TypeBuilder )。
接下来,您需要获取正确的方法。同样,这很糟糕,因为包装器还没有正确的方法——毕竟,我们仍在构建类型。 TypeBuilder 有一个静态方法,GetMethod
允许您使用泛型定义中的方法来查找新 brew 泛型事物中的方法。例如:
var baseMethod =
TypeBuilder.GetMethod(typeof(IEquatable<>).MakeGenericType(builder)),
typeof(IEquatable<>).GetMethod("Equals" /* add constraints */));
走这条路时要注意两点:
- 将程序集保存到磁盘,并对所有内容使用 PEVerify。 即使它运行!它会为您省去很多麻烦。
- 您可能想考虑简单地转换和使用
object
或普通的非通用接口。只要您不使用值类型,IL 只会对代码进行一次 JIT,因此通常不会有性能损失。 (如果你有兴趣,我在 SO 上对此有疑问)
由于 ILGenerator
可以做到这一点,我查看了 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 以简化一些反射):
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 });
这里,typeParameter
是从DefineGenericParameters
返回的GenericTypeParameterBuilder
。使用此代码,Reflector 显示了以下 IL,我相信这是您想要的(除了我将 Method1
设为静态并因此将 ldarg.0
替换为 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
}