Search code examples
c#functionclassdynamictypebuilder

C# TypeBuilder Generate Class with Function Dynamically


I'm trying to use TypeBuilder in C# to dynamically generate a class with a function, and to have that function call another base function.

The reason for the need for this is that in Revit application development, every button needs to have a class that implements IExternalCommand with an Execute function. I'd like to dynamically create buttons and handle their execution at runtime based on their ID, so I therefore need to dynamically create the classes too.

Hopefully this code gets across what I'm looking for ( or here http://pastebin.com/eehGKteT ):

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Reflection.Emit;

namespace Centek_Revit_Addin
{
    class DynamicButton
    {
        // I would like to use a function like this to generate the class during runtime, presumably using TypeBuilder:
        public static void generateClass(int id)
        {
            // ... Code which would generate a class with the name "GeneratedClass" with the 'id' parameter appended at the end
            // ... The class implements IExternalCommand
            // ... The class has an Execute function with the parameters listed in the example, which returns a call to the Execute function in DynamicButton
            //      along with the added integer 'id' parameter at the end
        }

        public static Autodesk.Revit.UI.Result Execute(ExternalCommandData revit, ref string message, ElementSet elements, int id)
        {
            TaskDialog.Show("About", "ID of the class that called us: " + id);
            return Autodesk.Revit.UI.Result.Succeeded;
        }
    }


    // ===== This class would have been generated during runtime using generateClass(15) ====== //
    class GeneratedClass15 : Autodesk.Revit.UI.IExternalCommand
    {
        public Autodesk.Revit.UI.Result Execute(Autodesk.Revit.UI.ExternalCommandData revit, ref string message, Autodesk.Revit.DB.ElementSet elements)
        {
            return DynamicButton.Execute(revit, ref message, elements, 15);
        }
    }
    // =================================================================== //
}

I tried to get a TypeBuilder working and I figured out the basics but I just can't seem to figure out how to use to Opcodes to get the class how I'd like it.

So basically I am looking for help writing the generateClass(int id) function. Any help would be HUGELY appreciated!

EDIT:

I would like to add my progress:
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Reflection.Emit;

namespace Centek_Revit_Addin
{
    class DynamicButton
    {
        // I would like to use a function like this to generate the class during runtime, presumably using TypeBuilder:
        public static void generateClass(int id)
        {
            // ... Code which would generate a class with the name "GeneratedClass" with the 'id' parameter appended at the end
            // ... The class implements IExternalCommand
            // ... The class has an Execute function with the parameters listed in the example, which returns a call to the Execute function in DynamicButton
            //      along with the added integer 'id' parameter at the end

            AssemblyName aName = new AssemblyName("DynamicAssemblyExample");
            AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndSave);

            // For a single-module assembly, the module name is usually 
            // the assembly name plus an extension.
            ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");

            // Create class which extends Object and implements IExternalCommand
            Type[] implements = {typeof(IExternalCommand)};
            TypeBuilder tb = mb.DefineType("GeneratedClass" + id, TypeAttributes.Public, typeof(Object), implements);


            // Create 'Execute' function sig
            Type[] paramList = {typeof(ExternalCommandData), typeof(string), typeof(ElementSet)};
            MethodBuilder mbExecute = tb.DefineMethod("Execute", MethodAttributes.Public, typeof(Result), paramList);

            // Create 'Execute' function body
            ILGenerator ilGen = mbExecute.GetILGenerator();

            ilGen.Emit(OpCodes.Nop);
            ilGen.Emit(OpCodes.Ldarg_1);
            ilGen.Emit(OpCodes.Ldarg_2);
            ilGen.Emit(OpCodes.Ldarg_3);

            ilGen.Emit(OpCodes.Ldc_I4_S, id);

            Type[] paramListWID = { typeof(ExternalCommandData), typeof(string), typeof(ElementSet), typeof(int) };
            ilGen.EmitCall(OpCodes.Call, typeof(DynamicButton).GetMethod("Execute"), paramListWID);


            //ilGen.Emit(OpCodes.Ret);


            tb.CreateType();
        }

        public static Autodesk.Revit.UI.Result Execute(ExternalCommandData revit, ref string message, ElementSet elements, int id)
        {
            TaskDialog.Show("About", "ID of the class that called us: " + id);
            return Autodesk.Revit.UI.Result.Succeeded;
        }
    }



    // ===== This class would have been generated during runtime using generateClass(15) ====== //
    class GeneratedClass15 : Autodesk.Revit.UI.IExternalCommand
    {
        public Autodesk.Revit.UI.Result Execute(Autodesk.Revit.UI.ExternalCommandData revit, ref string message, Autodesk.Revit.DB.ElementSet elements)
        {
            return DynamicButton.Execute(revit, ref message, elements, 15);
        }
    }
    // =================================================================== //
}

This code is much closer, but when running it I get the error

System.TypeLoadException: Method 'Execute' in type 'GeneratedClass99' from assembly 'DynamicAssemblyExample, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.

This error occurs when I call CreateType(..) in generateClass(..)


Solution

  • First you have to fix the parameters type you're using. Note that the message parameter has the refattribute, so you should change your typeof(String) to Type.GetType("System.String&").

    Afer that you have to state your execute method implements (overrides) the execute method from the interface:

    tb.DefineMethodOverride(mbExecute, typeof(IExternalCommand).GetMethod("Execute"));
    

    I did some tests with a consoleapplication and with the changes above I was able to get it working:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Reflection;
    using System.Reflection.Emit;
    
    namespace ConsoleApplication10
    {
        class Program
        {
            static void Main(string[] args)
            {
                int a;
                string s = "";
                while ((a = int.Parse(Console.ReadLine())) != 0)
                {
    
    
                    var t = DynamicButton.generateClass(a);
    
                    ((IExternalCommand)t.GetConstructor(new Type[0]).Invoke(new object[0])).Execute(null, ref s, null);
                }
            }
        }
    
        public interface IExternalCommand
        {
            Result Execute(ExternalCommandData revit, ref string message, ElementSet elements);
        }
    
        public class DynamicButton
        {
            // I would like to use a function like this to generate the class during runtime, presumably using TypeBuilder:
            public static Type generateClass(int id)
            {
                // ... Code which would generate a class with the name "GeneratedClass" with the 'id' parameter appended at the end
                // ... The class implements IExternalCommand
                // ... The class has an Execute function with the parameters listed in the example, which returns a call to the Execute function in DynamicButton
                //      along with the added integer 'id' parameter at the end
    
                AssemblyName aName = new AssemblyName("DynamicAssemblyExample");
                AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndSave);
    
                // For a single-module assembly, the module name is usually 
                // the assembly name plus an extension.
                ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");
    
                // Create class which extends Object and implements IExternalCommand
                var implements = new Type[] {typeof(IExternalCommand)};
                TypeBuilder tb = mb.DefineType("GeneratedClass" + id, TypeAttributes.Public, typeof(Object), implements);
    
    
                // Create 'Execute' function sig
                Type[] paramList = {typeof(ExternalCommandData), Type.GetType("System.String&"), typeof(ElementSet)};
                MethodBuilder mbExecute = tb.DefineMethod("Execute", MethodAttributes.Public | MethodAttributes.Virtual, typeof(Result), paramList);
    
                // Create 'Execute' function body
                ILGenerator ilGen = mbExecute.GetILGenerator();
    
                ilGen.Emit(OpCodes.Nop);
                ilGen.Emit(OpCodes.Ldarg_1);
                ilGen.Emit(OpCodes.Ldarg_2);
                ilGen.Emit(OpCodes.Ldarg_3);
    
                ilGen.Emit(OpCodes.Ldc_I4_S, id);
    
                Type[] paramListWID = { typeof(ExternalCommandData), Type.GetType("System.String&"), typeof(ElementSet), typeof(int) };
                ilGen.EmitCall(OpCodes.Call, typeof(DynamicButton).GetMethod("Execute"), paramListWID);
    
    
                ilGen.Emit(OpCodes.Ret);
    
    
    
                tb.DefineMethodOverride(mbExecute, typeof(IExternalCommand).GetMethod("Execute"));
                return tb.CreateType();
            }
    
            public static Result Execute(ExternalCommandData revit, ref string message, ElementSet elements, int id)
            {
                Console.WriteLine("About {0}", "ID of the class that called us: " + id);
                return Result.Succeeded;
            }
        }
    
        public enum Result
        {
            Succeeded
        }
    
        public class ExternalCommandData { }
        public class ElementSet { }
        // =================================================================== //
    }