A Ovest Di Paperino

Welcome to the dark side.

Metodi dinamici di delegazione

Questi giorni sto giochicchiando con i DynamicMethod di .Net e ho pensato di condividere. Lo scopo di tali classi - conosciute anche come lightweight code generation - è di generare codice IL (il P-code di .Net) dinamicamente. Accoppiandoli con i metodi delegate (puntatori a funzione) si possono fare cose veramente interessanti. Gli esempi che seguono sono alquanto banali (il classico "Hello World"), ma con la LCG si possono fare cose moooolto deliziose: esempio 1, esempio 2.

Altrettanto interessante è il funzionamento della Virtual Machine stack based di .Net: il set di istruzioni è documentato qui ma se si vuole giocare seriamente a questo tipo di giochi pericolosissimi (roba impossibile da debuggare capace di creare codice a fungo atomico in men che non si dica) consiglio _IL_ testo di riferimento che è "Compiling for the .Net Language Runtime" il cui unico neo è quello di essere basato sulla CLR 1.0 (niente template e altre corbellerie "moderne"). Volendo, la via più breve per capire l'IL da generare, è l'uso "a posteriori" di reflector.

Ed ora gli esempi, giusto per stimolare l'appetito:

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

public class TestClass
{
    string message;
    public delegate void TestMethod();
    TestMethod myTestMethod;

    public TestMethod MyTestMethod
    {
        get { return myTestMethod; }
    }

    public TestClass(string aMessage)
    {
        this.message = aMessage;
    }

    public void RealTestMethod()
    {
        Console.WriteLine(this.message);
    }

    public void CreateMethod()
    {
        /*
         * this is equivalent to:
         * Console.WriteLine("Hello world!");
         * there is no access to object members 
         * so the method can be declared as 'static'
         * 
         */
          
        // declare the method signature as void HelloWorld()
        //
        DynamicMethod dm = 
            new DynamicMethod("HelloWorld",  // name of the method
            typeof(void),           // return type
            new Type[] { },         // input types
            typeof(TestClass)); // class to 'attach the method to
        ILGenerator il = dm.GetILGenerator();

        // create a constanst string using the 
        // message and put it on the stack
        il.Emit(OpCodes.Ldstr, this.message);

        // invoke Console.WriteLine(string)
        // since it's a static method it only 
        // requires a string (the one on the stack)
        il.Emit(OpCodes.Call, 
            typeof(Console).GetMethod("WriteLine", 
            new Type[] { typeof(string) }));

        // return
        il.Emit(OpCodes.Ret);

        // create a delegate
        this.myTestMethod = 
            (TestMethod) dm.CreateDelegate(
            typeof(TestMethod)
            );
    }


    public void CreateMethod2()
    {
        /*
         * this is equivalent to:
         * Console.WriteLine(this.message);
         * there is access to object members 
         * so the method cannot be declared as 'static'
         * 
         */

        // declare the method signature as 
        // void HelloWorld(TestClass implicitThis)
        // it will be mapped to 
        // TestClass.HelloWorld() as instance method
        //
        DynamicMethod dm = new DynamicMethod(
            "HelloWorld",                      // name of the method
            typeof(void),                      // return type
            new Type[] { typeof(TestClass) },  // input types
            typeof(TestClass));     // class to 'attach' the method to
        ILGenerator il = dm.GetILGenerator();

        // puts the arg_o (this) on the stack
        il.Emit(OpCodes.Ldarg_0);

        // puts (POP)->message on the stack
        il.Emit(OpCodes.Ldfld, typeof(TestClass).GetField("message", 
            BindingFlags.NonPublic | BindingFlags.Instance));

        // invoke Console.WriteLine(string)
        // since it's a static method it only 
        // requires a string (the one on the stack)
        il.Emit(OpCodes.Call, 
            typeof(Console).GetMethod(
                "WriteLine", new Type[] { typeof(string) }
                )
            );

        // return
        il.Emit(OpCodes.Ret);

        // create a delegate
        this.myTestMethod = 
            (TestMethod)dm.CreateDelegate(typeof(TestMethod), this);
    }
}


public class HelloWorld
{
 
    public static void Main(string[] args)
    {
        TestClass myTestClass = new TestClass("Hello World!");
        myTestClass.CreateMethod();
        myTestClass.MyTestMethod();

        myTestClass.CreateMethod2();
        myTestClass.MyTestMethod();
    }

}

-quack