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
(..)
First you have to fix the parameters type you're using. Note that the message
parameter has the ref
attribute, 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 { }
// =================================================================== //
}