2015-11-06 70 views
6

我有這樣的代碼來發送加載整數或字符串值的IL代碼。但我不知道如何添加decimal類型。它在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)); 
} 

編輯:好了,這裏就是我所做的:

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操作碼,但而不是nopstloc.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 -decimal /。簡而言之:小數不是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方法,讓你用適當的值調用更簡單的構造函數,或使用反射來讀取內部狀態 - 有很多選項。

編輯:

你接近,但你打破了伊爾根 - 而最後一個參數的構造函數是一個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); 

編輯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); 
}