2015-11-06 2 views
6

У меня есть код, подобный этому, чтобы испустить код IL, который загружает целочисленные или строковые значения. Но я не знаю, как добавить к этому тип decimal. Он не поддерживается в методе Emit. Любые решения?Emit IL код для загрузки десятичного значения

ILGenerator ilGen = methodBuilder.GetILGenerator(); 
if (type == typeof(int)) 
{ 
    ilGen.Emit(OpCodes.Ldc_I4, Convert.ToInt32(value, CultureInfo.InvariantCulture)); 
} 
else if (type == typeof(double)) 
{ 
    ilGen.Emit(OpCodes.Ldc_R8, Convert.ToDouble(value, CultureInfo.InvariantCulture)); 
} 
else if (type == typeof(string)) 
{ 
    ilGen.Emit(OpCodes.Ldstr, Convert.ToString(value, CultureInfo.InvariantCulture)); 
} 

Не работает:

else if (type == typeof(decimal)) 
{ 
    ilGen.Emit(OpCodes.Ld_???, Convert.ToDecimal(value, CultureInfo.InvariantCulture)); 
} 

Edit: Итак, вот что я сделал:

else if (type == typeof(decimal)) 
{ 
    decimal d = Convert.ToDecimal(value, CultureInfo.InvariantCulture); 
    // Source: https://msdn.microsoft.com/en-us/library/bb1c1a6x.aspx 
    var bits = decimal.GetBits(d); 
    bool sign = (bits[3] & 0x80000000) != 0; 
    byte scale = (byte)((bits[3] >> 16) & 0x7f); 
    ilGen.Emit(OpCodes.Ldc_I4, bits[0]); 
    ilGen.Emit(OpCodes.Ldc_I4, bits[1]); 
    ilGen.Emit(OpCodes.Ldc_I4, bits[2]); 
    ilGen.Emit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); 
    ilGen.Emit(OpCodes.Ldc_I4, scale); 
    var ctor = typeof(decimal).GetConstructor(new[] { typeof(int), typeof(int), typeof(int), typeof(bool), typeof(byte) }); 
    ilGen.Emit(OpCodes.Newobj, ctor); 
} 

Но это не создает newobj опкод, но вместо nop и stloc.0. Конструктор найден и передан в вызов Emit. Что здесь не так? Очевидно, что при попытке выполнить сгенерированный код бросается InvalidProgramException, потому что стек полностью перепутан.

+1

Видимо (но не верьте мне на слово) для «нагрузки десятичного» не существует прямой опкод, вы загружаете аргументы и вызвать десятичный конструктор: см HTTP : //stackoverflow.com/a/485834/266143 – CodeCaster

+1

См. также http://codeblog.jonskeet.uk/2014/08/22/when-is-a-constant-not-a-constant-when-its-a -десятичная дробь/. Короче говоря, десятичные знаки не являются примитивными типами CLR, и нет никакого кода операции IL для загрузки одного напрямую. –

+0

См. Мое редактирование выше для нерабочего решения. – ygoe

ответ

9

Да, просто декомпилируйте код C#, который делает то же самое - вы увидите, что нет десятичного примитива.

42M 

компилирует

ldc.i4.s 2A 
newobj  System.Decimal..ctor 

Для десятичного числа, это гораздо сложнее:

42.3M 

дает

ldc.i4  A7 01 00 00 
ldc.i4.0  
ldc.i4.0  
ldc.i4.0  
ldc.i4.1  
newobj  System.Decimal..ctor 

Самый простой способ получить это для произвольного десятичного знака - использовать перегрузку конструктора int[] и статический метод GetBits. Вы также можете перепроектировать метод SetBits, чтобы вы могли называть более простой конструктор с правильными значениями или использовать отражение для чтения внутреннего состояния - есть много вариантов.

EDIT:

Вы близки, но ты нарушил Ilgen - в то время как последний аргумент конструктора является byte константа вы загружаете должны быть int. Следующие работы, как и ожидалось:

var bits = decimal.GetBits(d); 
bool sign = (bits[3] & 0x80000000) != 0; 
int scale = (byte)((bits[3] >> 16) & 0x7f); 
gen.Emit(OpCodes.Ldc_I4, bits[0]); 
gen.Emit(OpCodes.Ldc_I4, bits[1]); 
gen.Emit(OpCodes.Ldc_I4, bits[2]); 
gen.Emit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); 
gen.Emit(OpCodes.Ldc_I4, scale); 
var ctor = typeof(decimal).GetConstructor(new[] { typeof(int), typeof(int), 
               typeof(int), typeof(bool), typeof(byte) }); 
gen.Emit(OpCodes.Newobj, ctor); 
gen.Emit(OpCodes.Ret); 

EDIT 2:

Простой пример того, как можно использовать деревья выражений (в этом случае дерево строится на C# компилятор, но это до вас) для определения динамических тел методов:

var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Test"), 
                AssemblyBuilderAccess.Run); 
var module = assembly.DefineDynamicModule("Test"); 
var type = module.DefineType("TestType"); 

var methodBuilder = type.DefineMethod("MyMethod", MethodAttributes.Public 
                | MethodAttributes.Static); 
methodBuilder.SetReturnType(typeof(decimal)); 

Expression<Func<decimal>> decimalExpression =() => 42M; 

decimalExpression.CompileToMethod(methodBuilder); 

var t = type.CreateType(); 

var result = (decimal)t.GetMethod("MyMethod").Invoke(null, new object[] {}); 

result.Dump(); // 42 :) 
+3

Это, по-видимому, потому, что _ «Библиотека расширенной нумерации» _ не является частью спецификации CIL, потому что «некоторые общедоступные процессоры не обеспечивают прямую поддержку типов данных» _ (источник: http: //www.ecma -international.org/publications/files/ECMA-ST/ECMA-335.pdf, большой PDF). Вот почему нет кода операции для загрузки 'decimal' (а не' single'). – CodeCaster

+0

Спасибо за подсказку. К сожалению, он все еще работает неправильно. См. Мое редактирование в вопросе. – ygoe

+1

@LonelyPixel Обновлен с правильным кодом - 'ldc.i4' * должен * быть передан' int'. Жаль, что ILGen позволит вам это сделать, но вам просто нужно быть осторожным :) Однако вам не нужно, чтобы ILGen так много в наши дни - почему бы не использовать 'Expression.Compile'? – Luaan

0

Как Luaan упоминалось ранее, вы можете использовать метод decimal.GetBits и int[] конструктор.Посмотрите на этом примере:

public static decimal RecreateDecimal(decimal input) 
{ 
    var bits = decimal.GetBits(input); 

    var d = new DynamicMethod("recreate", typeof(decimal), null); 
    var il = d.GetILGenerator(); 

    il.Emit(OpCodes.Ldc_I4_4); 
    il.Emit(OpCodes.Newarr, typeof(int)); 

    il.Emit(OpCodes.Dup); 
    il.Emit(OpCodes.Ldc_I4_0); 
    il.Emit(OpCodes.Ldc_I4, bits[0]); 
    il.Emit(OpCodes.Stelem_I4); 

    il.Emit(OpCodes.Dup); 
    il.Emit(OpCodes.Ldc_I4_1); 
    il.Emit(OpCodes.Ldc_I4, bits[1]); 
    il.Emit(OpCodes.Stelem_I4); 

    il.Emit(OpCodes.Dup); 
    il.Emit(OpCodes.Ldc_I4_2); 
    il.Emit(OpCodes.Ldc_I4, bits[2]); 
    il.Emit(OpCodes.Stelem_I4); 

    il.Emit(OpCodes.Dup); 
    il.Emit(OpCodes.Ldc_I4_3); 
    il.Emit(OpCodes.Ldc_I4, bits[3]); 
    il.Emit(OpCodes.Stelem_I4); 

    il.Emit(OpCodes.Newobj, typeof(decimal).GetConstructor(new[] {typeof(int[])})); 

    il.Emit(OpCodes.Ret); 
    return (decimal) d.Invoke(null, null); 
}