首頁‎ > ‎電子期刊‎ > ‎2010年2月號‎ > ‎

.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.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的順序分別為:
    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。


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


Comments