.NET Emit技術初探(上) 建立Property的存取方法 在定義了屬性以及欄位後,接下來要定義的屬性的方法,方法分為兩個,第一個是Get方法用來取存欄位值,第二個是Set方法用來設定欄位;詳細的說明放在註解當中。 //定義存取方法的屬性,其中要做為類別屬性的存取方法,必需要有特別的屬性,亦即要有MethodAttributes.SpecialName及MethodAttributes.HideSig。http://dev.ischool.com.tw/wiki/index.php/User:ChangKH MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; //定義取得方法,第一個參數是方法名稱,第二個參數是描述方法的屬性,第三個參數是傳回的型別,第四個參數是傳入的型別。 MethodBuilder mbNumberGetAccessor = typebuilder.DefineMethod ( "get_"+Field.Name, getSetAttr, type(string), Type.EmptyTypes); //定義取得方法的中介語言,第二行是代表將我們剛才定義的FieldBuilder欄位值傳回。 ILGenerator numberGetIL = mbNumberGetAccessor.GetILGenerator(); numberGetIL.Emit(OpCodes.Ldarg_0); numberGetIL.Emit(OpCodes.Ldfld, FBuilder); numberGetIL.Emit(OpCodes.Ret); //定義設定方法,其中第一個參數是方法名稱,第二個參數是描述方法的屬性,第三個參數是傳入的參數型別,一般都是在屬性的設定都是藉由value參數來設定,所以此處傳入null,最後一個參數則是傳回的型別。 MethodBuilder mbNumberSetAccessor = typeBuilder.DefineMethod( "set_"+Field.Name, getSetAttr, null, new Type[] { type(string)}); ILGenerator numberSetIL = mbNumberSetAccessor.GetILGenerator(); //定義設定方法的中介語言,第三行是將傳入值設定到我們定義的FieldBuilder欄位。 numberSetIL.Emit(OpCodes.Ldarg_0); numberSetIL.Emit(OpCodes.Ldarg_1); numberSetIL.Emit(OpCodes.Stfld, FBuilder); numberSetIL.Emit(OpCodes.Ret); //在定義好存取及設定方法後,接下來將方法加入到屬性當中。 PBuilder.SetGetMethod(mbNumberGetAccessor); PBuilder.SetGetMethod(mbNumberSetAccessor); |
建立Property的Attribue 最後建立Property的Attribute,在建立Attribute時需要注意,CustomAttributeBuilder所接受的建立方式,是運用CustomAttribute的建構式來指定值,而無法在建立CustomAttribute的實體後再指定其值;所以若是CustomAttribute的屬性沒有在對應的建構式參數中對映,就會沒辦法指定CustomAttribute的值,而必需修改CustomAttribute的定義才可。 //建立CustomAttribute類別的建構式實體,若是建構式沒有參數則傳入Type.EmptyTypes,若是有參數,則傳入如下的參數列表。 ConstructorInfo propertyCtorInfo = typeof(FieldAttribute).GetConstructor(new Type[] { typeof(string), typeof(bool) }); //接下來建立CustomAttributeBuilder實體,將剛才的建構式參數傳入,並且指定實際的CustomAttribute值。 CustomAttributeBuilder ABuilder = new CustomAttributeBuilder(propertyCtorInfo,new object[] { Field.Name, Field.TypeName.StartsWith("bool") ? false : true }); //最後運用PropertyBuilder來加入CustomAttributeBuilder。 PBuilder.SetCustomAttribute(ABuilder); |
建立實體 我們在建立好了型別,問題是要如何把它轉成實際可被建立的型別呢?可以運用TypeBuilder的CreateType()方法來達成。 typeBuilder.CreateType(); |
接下可用如下的程式碼來建立實體。 Type myType = typeBuilder.CreateType(this); System.Reflection.ConstructorInfo creater = myType .GetConstructor(Type.EmptyTypes); object TypeInstance = creater.Invoke(null); |
建立Interface 接下來的這個範例介紹如何動態建立介面。 //引用命名空間。 using System.Reflection; using System.Reflection.Emit; //宣告輸出路徑以及輸出檔名。 string outputdir = "F:\\tmp\\"; string fname = "Hello.World.dll"; //建立AssemblyName物件,指定Assembly的名稱及版本編號。 AssemblyName bAssemblyName = new AssemblyName(); bAssemblyName.Name = "Hello.World"; bAssemblyName.Version = new system.Version(1,2,3,4); //取得目前的AppDomain,並且定義Assembly,將Assembly設定成可以儲存,並傳入儲存路徑。 AssemblyBuilder bAssembly = System.AppDomain.CurrentDomain.DefineDynamicAssembly(bAssemblyName, AssemblyBuilderAccess.Save, outputdir); //運用AssemblyBuilder物件來建立模組。 ModuleBuilder bModule = bAssembly.DefineDynamicModule(fname, true); //運用ModuleBuilder物件來建立型別,其中第一個參數為介面名稱,第二個參數為型別屬性,指定為介面,並且是公開的。 TypeBuilder tInterface = bModule.DefineType("IFoo", TypeAttributes.Interface | TypeAttributes.Public); //取得FunAttribute的建構式,欲在介面上加自訂標籤。 ConstructorInfo con = typeof(FunAttribute).GetConstructor(new Type[] { typeof(string) }); //建立CustomAttributeBuilder物件,將建構式及建構式的參數傳入。 CustomAttributeBuilder cab = new CustomAttributeBuilder(con, new object[] { "Hello" }); //將自訂標籤加入到介面中。 tInterface.SetCustomAttribute(cab); //實際產生介面型別。 Type tInt = tInterface.CreateType(); //儲存Assembly。 bAssembly.Save(fname); |
namespace Hello.World { [Fun("Hello")] public interface IFoo {} } |
重點歸納 - .NET Emit主要的目的在於在執行時期能動態的建立Assembly、Module、Type、Property、Event、Field、Interface…等;並且也可以執行時期動態的撰寫Common Intermediate Language。
- 使用.NET Emit的順序分別為:
- 取得目前的AppDomain,可以使用AppDomain.CurrentDomain或是Thread.GetDomain()方法。
- 運用取得的AppDomain物件來建立Assembly,會得到AssemblyBuilder物件。
- 運用取得的AssemblyBuilder物件來建立Module,會得到ModuleBuilder物件。
- 運用取得的ModuleBuilder物件來建立Type,會得到TypeBuilder物件。
- 運用取得的TypeBuilder來建立型別的相關變數、屬性、事件及方法。
- 熟悉Builder Pattern會對.NET Emit有更深入的瞭解,並且在使用上也更為方便,有關Builder Pattern的說可參考以下網址http://en.wikipedia.org/wiki/Builder_pattern。
解析HelloWorld程式的Intermediate Language 在之前的程式碼中,我們有運用到ILGenerator這個類別,這個類別用來定義Intermediate Language;底下以Hello World的範例,我們來看看IL的詳細定義,底下為HelloWorld類別的定義。 using System; class Hello { public static void Main (string[] args) { Console.writeLine ("Hello C#"); } } |
我們可以用ILDasm這個工具來反組譯HelloWorld所產生的Dll。  列表一:下面IL程式碼代表是類別的定義。 .class private auto ansi beforefieldinit Hello extends [mscorlib]System.Object { } |
列表二:下面IL程式碼是代表類別的建構式定義。 method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } |
列表三:下面的IL定義是代表Main方法的定義。 .method public hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 11 (0xb) .maxstack 8 IL_0000: ldstr "Hello C#" IL_0005: call void [mscorlib]System.Console:: WriteLine(string) IL_000a: ret } |
|