Question

I'm having an issue with emitting IL to set a uint64 property value. Below is some minimal code to reproduce the issue.

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");
    }
  }
}

If you run peverify on the created .dll you get the following:

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

If you dissassemble the constructor it looks like follows:

.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

So why is ilGenerator.Emit(OpCodes.Ldc_I8, ulong.Parse("0")); being turned into IL_0007: ldc.i8 0x22800000000 and what happened to the property set call?

Changing ulong.Parse to long.Parse gets it to create an assembly without these errors but then the long.Parse will crash if I feed it a value greater than long.MaxValue.

Était-ce utile?

La solution

That particular call to Emit ends up using the overload that accepts a float, due to the lack of a ulong-aware overload, as you've discovered since posting your original question. The funny-looking constant you see in ILDASM is mainly because that overload will only have emitted four bytes for the 0 value, but an 8 byte constant was expected, so it interpreted the following opcodes as the higher order bytes of the constant (which also explains why the property accessor call was apparently replaced with a completely different opcode).

Autres conseils

The trick is to use the unchecked keyword to convert the ulong to a long to make the emit operation happy.

  ilGenerator.Emit(OpCodes.Ldc_I8, unchecked((long)ulong.Parse("0")));
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top