我正在尝试在C#中使用TypeBuilder来动态生成带有函数的类,并让该函数调用另一个基函数。
需要这样做的原因是,在Revit应用程序开发中,每个按钮都需要一个使用Execute函数实现IExternalCommand的类。我想基于它们的ID动态创建按钮并在运行时处理它们的执行,因此我也需要动态创建类。
希望这段代码可以解决我正在寻找的问题(或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);
}
}
// =================================================================== //
}
我试图让TypeBuilder工作,我想出了基础知识,但我似乎无法弄清楚如何使用Opcodes来获得我喜欢的课程。
所以基本上我正在寻找编写generateClass(int id)函数的帮助。任何帮助都会非常感激!
编辑:
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);
}
}
// =================================================================== //
}
此代码更接近,但在运行时我收到错误
System.TypeLoadException:'GeneratedClass99'类型中的方法'Execute' 从程序集'DynamicAssemblyExample,Version = 0.0.0.0, Culture = neutral,PublicKeyToken = null'没有实现。
我在CreateType
(..)
generateClass
(..)时发生此错误
答案 0 :(得分:1)
首先,您必须修复正在使用的参数类型。请注意,message
参数具有ref
属性,因此您应将typeof(String)
更改为Type.GetType("System.String&")
。
你必须说明你的execute方法从接口实现(覆盖)execute方法:
tb.DefineMethodOverride(mbExecute, typeof(IExternalCommand).GetMethod("Execute"));
我使用控制台应用程序进行了一些测试,并且通过上述更改,我能够使其正常工作:
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 { }
// =================================================================== //
}