码.使用原始IL调用不同程序集中的泛型方法

本文关键字:程序集 程序 集中 泛型方法 原始 IL 调用 | 更新日期: 2023-09-27 18:02:42

我想使用原始IL指令调用一个泛型方法。为了学习如何做到这一点,我使用反射。发出和试验动态程序集。

我想调用的方法如下:

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

码.使用原始IL调用不同程序集中的泛型方法

既然ILGenerator可以做到这一点,我就看看它是怎么做的。我发现你需要一个MethodSpec令牌,但要得到它,你需要调用几个内部方法,就像上面的链接代码一样(我使用了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 });

这里,typeParameterDefineGenericParameters返回的GenericTypeParameterBuilder。使用此代码,Reflector显示以下IL,我相信这是您想要的(除了我使Method1静态,因此用nop替换ldarg.0):

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

首先,确保使用正确的callcallvirt。如果你有一个引用类型,通常最好使用callvirt,因为它也会隐式地添加一个null检查——但除此之外,如果你使用任何类型继承,你的PEVerify将失败,因此会导致未定义的行为。

接下来,我必须补充一点,找到要调用的正确方法通常是相当麻烦的。

您可能希望避免使用MakeGenericMethod,因为这意味着您还需要使用GetMethod,这可能会给您不正确的过载。实现你自己的反映。net的重载解析规则是可能的,但是相当困难(我在某个地方有一篇关于SO的文章,其中包括解决方案)。

最简单的方法是创建泛型类型并调用方法。基本上你可以这样做:

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

将给你一个包装器,其中包含你正在使用的TypeBuilder的泛型类型。(假设someType是一个TypeBuilder,如果我正确理解你的问题)。

接下来,您需要获得正确的方法。同样,这很糟糕,因为包装器还没有正确的方法——毕竟,我们还在构建类型。TypeBuilder有一个静态方法,GetMethod,它允许你使用泛型定义中的方法来查找新生成的泛型事物中的方法。例如:

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

当你沿着这条路走下去的时候需要注意两点:

  1. 将程序集保存到磁盘,并对所有内容使用PEVerify。即使它运行!这会给你省去很多麻烦。
  2. 您可能想要考虑简单地强制转换并使用object或普通的非泛型接口。只要不使用值类型,IL只会对代码进行一次JIT,因此通常不会有性能损失。(如果你感兴趣的话,我有一个关于SO的问题)