IL Code-Generierung mit System.Reflection.Emit

Thomas Darimont

Erfahrenes Mitglied
Hallo,

hier mal ein kleines Beispiel wie man mit System.Reflection.Emit dynamisch zur Laufzeit neuen IL-Code erzeugen lassen / und ausführen kann.

In meinem Beispiel habe ich ein einfach Programm zum finden von Primzahlen.
Das Interface IPrimeFinder deklariert die Methode bool IsPrime(int number);
Die Klasse PrimeFinder implementiert das IPrimeFinder und verwendet einen einfachen Algorithmus zur Entscheidung ob eine Zahl prim ist oder nicht:
C#:
 public bool IsPrime(int number)
        {

            if (number < 2)
            {
                return false;
            }

            if (2 == number)
            {
                return true;
            }

            if (0 == number % 2)
            {
                return false;
            }

            int sqrt = (int)Math.Sqrt(number);
            for (int i = 3; i <= sqrt; i += 2)
            {
                if (0 == number % i)
                {
                    return false;
                }
            }

            return true;
        }

Diesen Code möchte ich gerne 1:1 in IL zur Laufzeit generieren. Dazu erzeuge ich zur Laufzeit dynamisch einen neuen Type innerhalb einer dynamisch erzeugten Assembly welcher dann das Interface IPrimeFinder implementiert.

Wenn man sich den obigen Algorithmus mal in IL Code anschaut (beispielsweise mit dem .Net Reflector http://www.tutorials.de/forum/net-cafe/229672-net-decompiler-reflector.html ) so sieht man folgendes:

Code:
.method public hidebysig newslot virtual final instance bool IsPrime(int32 number) cil managed
{
      .maxstack 3
      .locals init (
            [0] int32 sqrt,
            [1] int32 i,
            [2] bool CS$1$0000,
            [3] bool CS$4$0001)
      L_0000: nop 
      L_0001: ldarg.1 
      L_0002: ldc.i4.2 
      L_0003: clt 
      L_0005: ldc.i4.0 
      L_0006: ceq 
      L_0008: stloc.3 
      L_0009: ldloc.3 
      L_000a: brtrue.s L_0011
      L_000c: nop 
      L_000d: ldc.i4.0 
      L_000e: stloc.2 
      L_000f: br.s L_0067
      L_0011: ldc.i4.2 
      L_0012: ldarg.1 
      L_0013: ceq 
      L_0015: ldc.i4.0 
      L_0016: ceq 
      L_0018: stloc.3 
      L_0019: ldloc.3 
      L_001a: brtrue.s L_0021
      L_001c: nop 
      L_001d: ldc.i4.1 
      L_001e: stloc.2 
      L_001f: br.s L_0067
      L_0021: ldc.i4.0 
      L_0022: ldarg.1 
      L_0023: ldc.i4.2 
      L_0024: rem 
      L_0025: ceq 
      L_0027: ldc.i4.0 
      L_0028: ceq 
      L_002a: stloc.3 
      L_002b: ldloc.3 
      L_002c: brtrue.s L_0033
      L_002e: nop 
      L_002f: ldc.i4.0 
      L_0030: stloc.2 
      L_0031: br.s L_0067
      L_0033: ldarg.1 
      L_0034: conv.r8 
      L_0035: call float64 [mscorlib]System.Math::Sqrt(float64)
      L_003a: conv.i4 
      L_003b: stloc.0 
      L_003c: ldc.i4.3 
      L_003d: stloc.1 
      L_003e: br.s L_0058
      L_0040: nop 
      L_0041: ldc.i4.0 
      L_0042: ldarg.1 
      L_0043: ldloc.1 
      L_0044: rem 
      L_0045: ceq 
      L_0047: ldc.i4.0 
      L_0048: ceq 
      L_004a: stloc.3 
      L_004b: ldloc.3 
      L_004c: brtrue.s L_0053
      L_004e: nop 
      L_004f: ldc.i4.0 
      L_0050: stloc.2 
      L_0051: br.s L_0067
      L_0053: nop 
      L_0054: ldloc.1 
      L_0055: ldc.i4.2 
      L_0056: add 
      L_0057: stloc.1 
      L_0058: ldloc.1 
      L_0059: ldloc.0 
      L_005a: cgt 
      L_005c: ldc.i4.0 
      L_005d: ceq 
      L_005f: stloc.3 
      L_0060: ldloc.3 
      L_0061: brtrue.s L_0040
      L_0063: ldc.i4.1 
      L_0064: stloc.2 
      L_0065: br.s L_0067
      L_0067: ldloc.2 
      L_0068: ret 
}
Diesen IL Code muss man nur noch mit den entsprechenden Mitteln (System.Reflection.Emit) etwas umformen und schon kanns losgehen :)

Das Resultat sieht man dann hier:
C#:
using System;
using System.Collections.Generic;
using System.Text;

using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace De.Tutorials.Training
{
    public class ILCodeGenerationExample
    {
        public static void Main(string[] args)
        {
            IPrimeFinder primeFinder = new PrimeFinder();
            IPrimeFinder generatedPrimeFinder = (IPrimeFinder)GeneratePrimeFinder();

            for (int i = 0; i < 1000;i++ )
            {
                if (primeFinder.IsPrime(i))
                {
                    Console.Write(i+" ");
                }
            }

            Console.WriteLine();

            for (int i = 0; i < 1000; i++)
            {
                if (generatedPrimeFinder.IsPrime(i))
                {
                    Console.Write(i + " ");
                }
            }

            Console.WriteLine();

            Console.WriteLine(int.MaxValue + " " + primeFinder.IsPrime(int.MaxValue));
            Console.WriteLine(int.MaxValue + " " + generatedPrimeFinder.IsPrime(int.MaxValue));
        }

        private static IPrimeFinder GeneratePrimeFinder()
        {
            AssemblyName generatedAssemblyName = new AssemblyName("DynamicAssembly#" + DateTime.Now.Ticks);
            AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(generatedAssemblyName, AssemblyBuilderAccess.RunAndSave);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(generatedAssemblyName.Name, generatedAssemblyName.Name + ".dll", true);
            TypeBuilder typeBuilder = moduleBuilder.DefineType("GeneratedPrimeFinder", TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit, typeof(object), new Type[] { typeof(IPrimeFinder) });
            MethodBuilder methodBuilder = typeBuilder.DefineMethod("IsPrime", MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final , typeof(bool), new Type[] { typeof(int) });
            typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
            ILGenerator methodIlGenerator = methodBuilder.GetILGenerator();

            //      .locals init (
            //[0] int32 sqrt,
            //[1] int32 i,
            //[2] bool CS$1$0000,
            //[3] bool CS$4$0001)
            LocalBuilder sqrtLocal = methodIlGenerator.DeclareLocal(typeof(int));
            sqrtLocal.SetLocalSymInfo("sqrt");
            LocalBuilder iLocal = methodIlGenerator.DeclareLocal(typeof(int));
            iLocal.SetLocalSymInfo("i");
            LocalBuilder cSS1S0000Local = methodIlGenerator.DeclareLocal(typeof(bool));
            cSS1S0000Local.SetLocalSymInfo("CSS1S0000");
            LocalBuilder cSS4S0001Local = methodIlGenerator.DeclareLocal(typeof(bool));
            cSS4S0001Local.SetLocalSymInfo("CSS4S0001");

            Label label0011 = methodIlGenerator.DefineLabel();
            Label label0021 = methodIlGenerator.DefineLabel();
            Label label0033 = methodIlGenerator.DefineLabel();
            Label label0040 = methodIlGenerator.DefineLabel();
            Label label0053 = methodIlGenerator.DefineLabel();
            Label label0058 = methodIlGenerator.DefineLabel();
            Label label0067Return = methodIlGenerator.DefineLabel();

            methodIlGenerator.Emit(OpCodes.Nop);
            methodIlGenerator.Emit(OpCodes.Ldarg_1);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_2);
            methodIlGenerator.Emit(OpCodes.Clt);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_0);
            methodIlGenerator.Emit(OpCodes.Ceq);
            methodIlGenerator.Emit(OpCodes.Stloc_3);
            methodIlGenerator.Emit(OpCodes.Ldloc_3);
            methodIlGenerator.Emit(OpCodes.Brtrue_S, label0011);
            methodIlGenerator.Emit(OpCodes.Nop);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_0);
            methodIlGenerator.Emit(OpCodes.Stloc_2);
            methodIlGenerator.Emit(OpCodes.Br_S, label0067Return);
            //Label label0011 = methodIlGenerator.DefineLabel();
            methodIlGenerator.MarkLabel(label0011);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_2);
            methodIlGenerator.Emit(OpCodes.Ldarg_1);
            methodIlGenerator.Emit(OpCodes.Ceq);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_0);
            methodIlGenerator.Emit(OpCodes.Ceq);
            methodIlGenerator.Emit(OpCodes.Stloc_3);
            methodIlGenerator.Emit(OpCodes.Ldloc_3);
            methodIlGenerator.Emit(OpCodes.Brtrue_S, label0021);
            methodIlGenerator.Emit(OpCodes.Nop);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_1);
            methodIlGenerator.Emit(OpCodes.Stloc_2);
            methodIlGenerator.Emit(OpCodes.Br_S, label0067Return);
            //Label label0021 = methodIlGenerator.DefineLabel();
            methodIlGenerator.MarkLabel(label0021);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_0);
            methodIlGenerator.Emit(OpCodes.Ldarg_1);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_2);
            methodIlGenerator.Emit(OpCodes.Rem);
            methodIlGenerator.Emit(OpCodes.Ceq);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_0);
            methodIlGenerator.Emit(OpCodes.Ceq);
            methodIlGenerator.Emit(OpCodes.Stloc_3);
            methodIlGenerator.Emit(OpCodes.Ldloc_3);
            methodIlGenerator.Emit(OpCodes.Brtrue_S, label0033);
            methodIlGenerator.Emit(OpCodes.Nop);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_0);
            methodIlGenerator.Emit(OpCodes.Stloc_2);
            methodIlGenerator.Emit(OpCodes.Br_S, label0067Return);
            //Label label0033 = methodIlGenerator.DefineLabel();
            methodIlGenerator.MarkLabel(label0033);
            methodIlGenerator.Emit(OpCodes.Ldarg_1);
            methodIlGenerator.Emit(OpCodes.Conv_R8);
            methodIlGenerator.Emit(OpCodes.Call, typeof(Math).GetMethod("Sqrt"));
            //methodIlGenerator.Emit(OpCodes.Call, float64 [mscorlib]System.Math::Sqrt(OpCodes.
            methodIlGenerator.Emit(OpCodes.Conv_I4);
            methodIlGenerator.Emit(OpCodes.Stloc_0);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_3);
            methodIlGenerator.Emit(OpCodes.Stloc_1);
            methodIlGenerator.Emit(OpCodes.Br_S, label0058);
            //Label label0040 = methodIlGenerator.DefineLabel();
            methodIlGenerator.MarkLabel(label0040);
            methodIlGenerator.Emit(OpCodes.Nop);
            methodIlGenerator.Emit(OpCodes.Ldarg_1);
            methodIlGenerator.Emit(OpCodes.Ldloc_1);
            methodIlGenerator.Emit(OpCodes.Rem);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_0);
            methodIlGenerator.Emit(OpCodes.Ceq);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_0);
            methodIlGenerator.Emit(OpCodes.Ceq);
            methodIlGenerator.Emit(OpCodes.Stloc_3);
            methodIlGenerator.Emit(OpCodes.Ldloc_3);
            methodIlGenerator.Emit(OpCodes.Brtrue_S, label0053);
            methodIlGenerator.Emit(OpCodes.Nop);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_0);
            methodIlGenerator.Emit(OpCodes.Stloc_2);
            methodIlGenerator.Emit(OpCodes.Br_S, label0067Return);
            //Label label0053 = methodIlGenerator.DefineLabel();
            methodIlGenerator.MarkLabel(label0053);
            methodIlGenerator.Emit(OpCodes.Nop);
            methodIlGenerator.Emit(OpCodes.Ldloc_1);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_2);
            methodIlGenerator.Emit(OpCodes.Add);
            methodIlGenerator.Emit(OpCodes.Stloc_1);
            //Label label0058 = methodIlGenerator.DefineLabel();
            methodIlGenerator.MarkLabel(label0058);
            methodIlGenerator.Emit(OpCodes.Ldloc_1);
            methodIlGenerator.Emit(OpCodes.Ldloc_0);
            methodIlGenerator.Emit(OpCodes.Cgt);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_0);
            methodIlGenerator.Emit(OpCodes.Ceq);
            methodIlGenerator.Emit(OpCodes.Stloc_3);
            methodIlGenerator.Emit(OpCodes.Ldloc_3);
            methodIlGenerator.Emit(OpCodes.Brtrue_S, label0040);
            methodIlGenerator.Emit(OpCodes.Ldc_I4_1);
            methodIlGenerator.Emit(OpCodes.Stloc_2);
            methodIlGenerator.Emit(OpCodes.Br_S, label0067Return);
            //Label label0067 = methodIlGenerator.DefineLabel();
            methodIlGenerator.MarkLabel(label0067Return);
            methodIlGenerator.Emit(OpCodes.Ldloc_2);
            methodIlGenerator.Emit(OpCodes.Ret);
            

            Type generatedPrimeFinderType = typeBuilder.CreateType();

            return (IPrimeFinder)Activator.CreateInstance(generatedPrimeFinderType);
        }
    }

    public interface IPrimeFinder
    {
        bool IsPrime(int number);
    }

    public class PrimeFinder : IPrimeFinder
    {
        public bool IsPrime(int number)
        {

            if (number < 2)
            {
                return false;
            }

            if (2 == number)
            {
                return true;
            }

            if (0 == number % 2)
            {
                return false;
            }

            int sqrt = (int)Math.Sqrt(number);
            for (int i = 3; i <= sqrt; i += 2)
            {
                if (0 == number % i)
                {
                    return false;
                }
            }

            return true;
        }
    }
}

Ausgabe:
Code:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997
2147483647 True
2147483647 True
Drücken Sie eine beliebige Taste . . .

Btw. wem das gefummel mit den Low-Level OpCodes zu mühsam ist der kann auch alternativ zum komfortablen http://wiki.castleproject.org/index.php/DynamicProxy greifen und die dortigen IL-Generierungsmechanismen verwenden (EasyType / EasyMethod etc.) damit wird's wirklich "Easy" :)

Mehr zum Thema (C)IL, der Architektur, einzelnen OpCodes findet man hier:
http://msdn2.microsoft.com/en-us/netframework/aa497266.aspx

Gruß Tom
 
Zurück