一樣,應該要注意的內容,我都註解到程式碼中,與前一篇「Visual Basic - Reflection, 反映教學筆記(8) 執行動態程式碼」很像,就是 Step by Step 的建立出你的組件。重點在於我們是使用 System.Reflection.Emit 類別裡 *Builder 結尾的類別,來動態建立出我們的組件。
*Builder 結尾的類別類似觀念,我們前面「」就已經談過,觀念一樣,我就不在重覆。你要建立什麼就是 ...Builder 類別。記的要先Imports System.Reflection.Emit。
RunTimeCreateAssembly 副程式
#Region "執行環境中產生程式碼" ''' <summary> ''' 建立動態組件 ''' </summary> Private Sub RunTimeCreateAssembly() ' 1. 建立組件 Console.WriteLine("定義組件") Dim tempName As New AssemblyName() tempName.Name = "KKBruceSampleAssembly" tempName.Version = New Version("1.0.0.0") ' 使用 DefineDynamicAssembly 動態產生組件 Dim AssemBL As AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(tempName, AssemblyBuilderAccess.RunAndSave) ' 2. 建立模組 Console.WriteLine("定義模組") ' DefineDynamicModule 動態產生模組 ' 如果要建立一個單一檔案的組件,並將此組件序列化在磁碟之中,最後的Save名稱必須與以下DefineDynamicModule裡名稱相同。 Dim ModuleBL As ModuleBuilder = AssemBL.DefineDynamicModule(tempName.Name, tempName.Name & ".dll") ' 3. 建立型別 Console.WriteLine("定義型別") ' DefineType 動態產生型別 Dim BruceType As TypeBuilder = ModuleBL.DefineType("KKBruceType", TypeAttributes.Class Or TypeAttributes.Public) ' 指定父類別及介面給DefineType 'Dim tb As TypeBuilder = mb.DefineType("KKBruceMyNewType", TypeAttributes.Class Or TypeAttributes.Public, ' GetType(Hashtable), ' New Type() {GetType(IDisposable)}) ' 4. 建立成員 Console.WriteLine("定義相關成員") ' 4.1 建立建構子 Console.WriteLine(" 定義建構子") ' MethodAttributes代表建構子的類型 'Dim ConstructorBL As ConstructorBuilder = BruceType.DefineDefaultConstructor(MethodAttributes.Public) ' 4.2 利用ConstructorBuilder建立ILGenerator物件 ' --- 註:執行會產生錯誤 --- ' --- 註:依MSDN查詢:Runtime 會為預設建構函式產生程式碼。因此,如果嘗試取得 ILGenerator,則會擲回例外狀況。 --- 'Console.WriteLine(" 建立ILGenerator物件") ' ILGenerator 可產生 Microsoft Intermediate Language (MSIL) 指令。 'Dim codeGen As ILGenerator = ConstructorBL.GetILGenerator() ' Emit:多載。放置指令到 Just-In-Time (JIT) 編譯器的 Microsoft Intermediate Language (MSIL) 資料流中。 ' OpCodes 類別:提供 Microsoft Intermediate Language (MSIL) 指令的欄位表示,以用於 ILGenerator 類別成員 (例如 Emit) 的發出。 ' Ret欄位:從目前方法傳回,將被呼叫端評估堆疊的傳回值 (如果有的話) 推入至呼叫端的評估堆疊。 'codeGen.Emit(OpCodes.Ret) ' 4.3 建立方法 Console.WriteLine(" 定義方法") Dim MethodBLOne As MethodBuilder = BruceType.DefineMethod("AddStringOne", MethodAttributes.Public, Nothing, New Type() {GetType(String)}) ' 第三個參數:建新新方法的回傳型別 ' 第四個參數:參數型別 Dim MethodBLTwo As MethodBuilder = BruceType.DefineMethod("AddStringTwo", MethodAttributes.Public Or MethodAttributes.Static, Nothing, New Type() {GetType(String)}) ' 4.4 建立私有欄位 Console.WriteLine(" 定義私有欄位") Dim FieldBL As FieldBuilder = BruceType.DefineField("_count", GetType(Int32), FieldAttributes.Private) ' 4.5 建立屬性 Console.WriteLine(" 定義屬性") ' PropertyAttributes.None 列舉常數不能定義所有的屬性 Dim PropertyBL As PropertyBuilder = BruceType.DefineProperty("Count", PropertyAttributes.None, GetType(Int32), Type.EmptyTypes) ' 4.6 在方法中定義設得或設定屬性值 Console.WriteLine(" 定義方法中設得或設定屬性值") ' 注意,使用MethodAttributes Dim getMethodAttributes As MethodAttributes = MethodAttributes.Public Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig Dim propGet As MethodBuilder = BruceType.DefineMethod("get_Count", getMethodAttributes, GetType(Int32), Type.EmptyTypes) ' 讓方法可以設定與取得屬性值 PropertyBL.SetGetMethod(propGet) ' 5. 建立型別 Console.WriteLine("產生新型別") ' --- 註:建立型別之前要為所有 MethodBuilder 執行 GetILGenerator() --- ' --- 註:不然會產生一個「方法 'xxx' 沒有方法主體。」的錯誤,這裡 Debug 花了我好久!XD --- ' --- 註:參考:http://msdn.microsoft.com/zh-tw/library/system.reflection.emit.methodbuilder.definegenericparameters%28VS.85%29.aspx 範例才找出解法 --- Console.WriteLine(" 產生所有方法的ILGenerator") RunMethodBLGetILGenerator(MethodBLOne) RunMethodBLGetILGenerator(MethodBLTwo) RunMethodBLGetILGenerator(propGet) Console.Write(" 產生型別") Dim KKBruceType As Type = BruceType.CreateType() ' 5.1 顯示此型別方法 Console.WriteLine() Console.WriteLine(" 顯示型別方法(注意我們產生動態產生的方法)") GetRunTimeAssemblyMethods(KKBruceType) Console.WriteLine() Console.ReadLine() ' 6. 儲存組件到磁碟中 Console.WriteLine("儲存組件到磁碟中...") ' 一但被寫入到磁碟中,任何程式都可以載入這個組件使用。 AssemBL.Save(tempName.Name & ".dll") Console.WriteLine("儲存完成。(請參考專案目錄下「\bin\Debug」產生的動態組件 " & tempName.Name & ".dll") Console.ReadLine() End Sub ''' <summary> ''' 傳回這個方法的 ILGenerator ''' </summary> ''' <param name="methodInstance">MethodBuilder 的執行個體</param> ''' <remarks></remarks> Private Sub RunMethodBLGetILGenerator(methodInstance As MethodBuilder) ' MethodBuilder.GetILGenerator() ' 傳回這個方法的 ILGenerator,使用預設 Microsoft Intermediate Language (MSIL) 資料流的 64 位元大小。 Dim ilgen As ILGenerator = methodInstance.GetILGenerator() ilgen.Emit(OpCodes.Ldnull) ilgen.Emit(OpCodes.Ret) End Sub ''' <summary> ''' 取得動態組件方法屬性 ''' </summary> ''' <param name="KKBruceType">型別</param> Private Sub GetRunTimeAssemblyMethods(ByVal KKBruceType As Type) Console.WriteLine(" Full Name: {0}", KKBruceType.FullName) For Each m As MemberInfo In KKBruceType.GetMethods() Console.WriteLine(" Member({0}):{1}", m.MemberType, m.Name) Next End Sub #End Region
執行結果
執行環境中產生程式碼 定義組件 定義模組 定義型別 定義相關成員 定義建構子 定義方法 定義私有欄位 定義屬性 定義方法中設得或設定屬性值 產生新型別 產生所有方法的ILGenerator 產生型別 顯示型別方法(注意我們產生動態產生的方法) Full Name: KKBruceType Member(Method):AddStringOne Member(Method):AddStringTwo Member(Method):get_Count Member(Method):ToString Member(Method):Equals Member(Method):GetHashCode Member(Method):GetType 儲存組件到磁碟中... 儲存完成。(請參考專案目錄下「\bin\Debug」產生的動態組件 KKBruceSampleAssembly.dll
注意我們自行新增的 Method (AddStringOne, AddStringTwo, get_Count),確認一下產生的 DLL 檔:
圖一:儲存動態程式碼為組件 |
參考資料
- System.Reflection.Emit 類別
- MethodBuilder.DefineGenericParameters 方法
- Visual Basic - Reflection, 反映教學筆記(1) 前言
- Visual Basic - Reflection, 反映教學筆記(2) 基礎反映了解
- Visual Basic - Reflection, 反映教學筆記(3) 取得特定屬性
- Visual Basic - Reflection, 反映教學筆記(4) 取得型別的4種方法
- Visual Basic - Reflection, 反映教學筆記(5) 取得型別相關屬性
- Visual Basic - Reflection, 反映教學筆記(6) 暸解MethodBody
- Visual Basic - Reflection, 反映教學筆記(7) - BindingFlags 過濾 Member(成員)
- Visual Basic - Reflection, 反映教學筆記(8) 執行動態程式碼
下載 Reflection (1) ~ (9) 範例程式碼
感謝你們看到這九篇文章,我將第一篇至第九篇的範例整理成一個小小小小的 Console 選擇遊戲,以下是原始碼專案,必須使用 Visual Studio 2010 / .NET Framework 4 才能重新編譯。已編譯執行檔在 \bin\Debug\AssemblyDemo.exe,如果你想試試我前面介紹的 Reflection 工具,也可以從已編譯執行檔裡去看原始碼。解壓縮密碼:KKBruce
此範例程式碼是「太濕版」一點也不「DRY, 乾」,全部用副程式在呼叫,下一篇我會將把此範例改寫為 OO (物件導向, Object-oriented) 的範例,算是大重構,不過從其中我們也能看到物件導向的優點,很多事你硬要使用「副程式/函式」來做是會…濕透了。
沒有留言:
張貼留言
感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。