2015-11-06 19 views
6

Ich habe Code wie diesen, um IL-Code zu emittieren, der Integer- oder String-Werte lädt. Aber ich weiß nicht, wie man dazu den decimal Typ hinzufügt. Es wird in der Emit Methode nicht unterstützt. Irgendwelche Lösungen dazu?Auslösen von IL-Code zum Laden eines Dezimalwerts

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

Problem:

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

Edit: Okay, hier ist das, was ich getan habe:

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

Aber es erzeugt keine newobj Opcode, sondern stattdessen nop und stloc.0. Der Konstruktor wird gefunden und an den Aufruf Emit übergeben. Was ist hier falsch? Offensichtlich wird ein InvalidProgramException geworfen, wenn versucht wird, den generierten Code auszuführen, da der Stapel vollständig durcheinander ist.

+1

Anscheinend (aber nehmen Sie nicht mein Wort dafür) für „Last dezimal“ gibt es keine direkte Opcode, laden Sie die Argumente und rufen Sie den Dezimal-Konstruktor: siehe http : //stackoverflow.com/a/485834/266143 – CodeCaster

+1

Siehe auch http://codeblog.jonskeet.uk/2014/08/22/when-is-a-constant-not-a-constant-when-its-a -Dezimal/. Kurz gesagt: Dezimale sind keine primitiven CLR-Typen und es gibt keinen IL-Opcode, um einen direkt zu laden. –

+0

Siehe meine Bearbeitung oben für eine nicht funktionierende Lösung. – ygoe

Antwort

9

Komm schon, dekompiliere einfach einen C# -Code, der das Gleiche tut - du wirst sehen, dass es kein dezimales Primitiv gibt.

42M 

kompiliert

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

Für eine Dezimalzahl, diese viel komplizierter ist:

42.3M 

gibt

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

Der einfachste Weg, dies für eine beliebige Dezimalzahl zu erhalten, ist die Verwendung der int[] Überladung des Konstruktors und der GetBits statischen Methode. Sie können auch die SetBits-Methode reverse-engineeren, damit Sie den einfacheren Konstruktor mit den richtigen Werten aufrufen können, oder verwenden Sie reflection, um den internen Status zu lesen - es gibt viele Optionen.

EDIT:

Sie sind in der Nähe, aber Sie Ilgen brach - während das letzte Argument für den Konstruktor ein byte ist, die Konstante Sie laden muss ein int sein. Die folgenden Arbeiten wie erwartet:

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:

Ein einfaches Beispiel dafür, wie man Ausdrucksbäume (in diesem Fall benutzen kann der Baum durch die C# Compiler gebaut wird, aber das ist bis zu Ihnen) dynamische Methodenrümpfe definieren:

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

Dies ist offensichtlich, weil _ "The Extended Numerics Library" _ ist nicht Teil der CIL-Spezifikation, weil _ "einige allgemein verfügbaren Prozessoren bieten keine direkte Unterstützung für die Datentypen" _ (Quelle: http: //www.ecma -international.org/publications/files/ECMA-ST/ECMA-335.pdf, großes PDF). Deshalb gibt es keinen Opcode zum Laden einer 'Dezimal' (noch einer' Single'). – CodeCaster

+0

Danke für die Hinweise. Leider funktioniert es immer noch nicht richtig. Siehe meine Bearbeitung in der Frage. – ygoe

+1

@LonelyPixel Mit dem richtigen Code aktualisiert - 'ldc.i4' * muss * ein' int' übergeben werden. Es ist eine Schande, dass ILGen Ihnen das erlauben wird, aber Sie müssen nur vorsichtig sein :) Allerdings brauchen Sie ILGen heutzutage nicht wirklich viel - warum nicht "Expression.Compile" verwenden? – Luaan

0

Wie Luaan bereits erwähnt, können Sie die decimal.GetBits-Methode und die int[] Konstruktor verwenden können.Werfen Sie einen Blick auf dieses Beispiel:

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