如何为ulong发射LDC_I8.分析调用

本文关键字:I8 调用 LDC ulong 发射 | 更新日期: 2023-09-27 18:26:49

我在发射IL以设置uint64属性值时遇到问题。下面是一些重现问题的最小代码。

using System;
using System.Reflection;
using System.Reflection.Emit;
namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
        new AssemblyName("test"), AssemblyBuilderAccess.RunAndSave);
      ModuleBuilder m_moduleBuilder = assemblyBuilder.DefineDynamicModule("test.dll",
        "test.dll");
      TypeBuilder typeBuilder = m_moduleBuilder.DefineType("Class1",
        TypeAttributes.Public |
        TypeAttributes.Class |
        TypeAttributes.AutoClass |
        TypeAttributes.AnsiClass |
        TypeAttributes.BeforeFieldInit |
        TypeAttributes.AutoLayout, null);
      FieldBuilder fieldBuilder = typeBuilder.DefineField("m_prop1",
          typeof(ulong), FieldAttributes.Private);
      MethodBuilder getMethodBuilder = typeBuilder.DefineMethod(
        "get_prop1",
        MethodAttributes.Public | MethodAttributes.SpecialName |
        MethodAttributes.HideBySig,
        typeof(ulong), Type.EmptyTypes);
      ILGenerator getIlGen = getMethodBuilder.GetILGenerator();
      getIlGen.Emit(OpCodes.Ldarg_0);
      getIlGen.Emit(OpCodes.Ldfld, fieldBuilder);
      getIlGen.Emit(OpCodes.Ret);
      MethodBuilder setMethodBuilder = typeBuilder.DefineMethod(
        "set_prop1",
        MethodAttributes.Public | MethodAttributes.SpecialName |
        MethodAttributes.HideBySig,
        null, new[] { typeof(ulong) });
      ILGenerator setIlGen = setMethodBuilder.GetILGenerator();
      setIlGen.Emit(OpCodes.Ldarg_0);
      setIlGen.Emit(OpCodes.Ldarg_1);
      setIlGen.Emit(OpCodes.Stfld, fieldBuilder);
      setIlGen.Emit(OpCodes.Ret);
      PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(
        "prop1", PropertyAttributes.HasDefault, typeof(ulong),
        null);
      propertyBuilder.SetGetMethod(getMethodBuilder);
      propertyBuilder.SetSetMethod(setMethodBuilder);
      ConstructorBuilder constructorBuilder =
        typeBuilder.DefineConstructor(MethodAttributes.Public,
        CallingConventions.Standard, Type.EmptyTypes);
      ILGenerator ilGenerator = constructorBuilder.GetILGenerator();
      ilGenerator.Emit(OpCodes.Ldarg_0);
      ilGenerator.Emit(OpCodes.Call,
        typeBuilder.BaseType.GetConstructor(Type.EmptyTypes));
      ilGenerator.Emit(OpCodes.Ldarg_0);
      ilGenerator.Emit(OpCodes.Ldc_I8, ulong.Parse("0"));
      ilGenerator.Emit(OpCodes.Call, propertyBuilder.GetSetMethod());
      ilGenerator.Emit(OpCodes.Ret);
      Type class1Type = typeBuilder.CreateType();
      assemblyBuilder.Save("test.dll");
    }
  }
}

如果你在创建的.dll上运行peverify,你会得到以下信息:

[IL]: Error: [C:'code'ConsoleApplication1'ConsoleApplication1'bin'Debug'test.dl
 : Class1::.ctor][offset 0x00000010] Unrecognized local variable number.
1 Error(s) Verifying test.dll

如果你分解构造函数,它看起来如下:

.method public specialname rtspecialname 
        instance void  .ctor() cil managed
{
  // Code size       18 (0x12)
  .maxstack  4
  IL_0000:  ldarg.0
  IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
  IL_0006:  ldarg.0
  IL_0007:  ldc.i8     0x22800000000
  IL_0010:  ldloc.0
  IL_0011:  ret
} // end of method Class1::.ctor

那么为什么ilGenerator.Emit(OpCodes.Ldc_I8, ulong.Parse("0"));会变成IL_0007: ldc.i8 0x22800000000和属性集调用发生了什么?

改变ulong。解析为长。Parse让它创建一个没有这些错误的程序集,然后是long。如果我给它一个大于long的值,Parse就会崩溃。最大值。

如何为ulong发射LDC_I8.分析调用

Emit的特定调用最终使用了接受float的重载,因为没有ulong感知的重载,正如您在发布原始问题后发现的那样。您在ILDASM中看到的看起来很有趣的常量主要是因为重载只会为0值发出四个字节,但需要一个8字节的常量,因此它将以下操作码解释为常量的高阶字节(这也解释了为什么属性访问器调用显然被一个完全不同的操作码取代)。

诀窍是使用unchecked关键字将ulong转换为long,以使发射操作愉快。

  ilGenerator.Emit(OpCodes.Ldc_I8, unchecked((long)ulong.Parse("0")));