期刊/.NET Emit技術初探(下)

出自台灣中等學校資訊管理人學會

跳轉到: 導航, 搜索

.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.SetSetMethod(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的順序分別為:
    1. 取得目前的AppDomain,可以使用AppDomain.CurrentDomain或是Thread.GetDomain()方法。
    2. 運用取得的AppDomain物件來建立Assembly,會得到AssemblyBuilder物件。
    3. 運用取得的AssemblyBuilder物件來建立Module,會得到ModuleBuilder物件。
    4. 運用取得的ModuleBuilder物件來建立Type,會得到TypeBuilder物件。
    5. 運用取得的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。

Ildasm.gif

列表一:下面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
}