Visual Basic - Reflection, 反映教學筆記(9) 執行環境中產生程式碼

總和前面所有,在這一篇裡,我們將整合前面所有內容,從無到有完完全全自行建立自有組件、自有模組、自有型別、自有成員、自有方法、自有欄位、自有屬性,最後還能將動態建立的組件儲存成實體組件檔案 ( *.dll )。

一樣,應該要注意的內容,我都註解到程式碼中,與前一篇「Visual Basic - Reflection, 反映教學筆記(8) 執行動態程式碼」很像,就是 Step by Step 的建立出你的組件。重點在於我們是使用 System.Reflection.Emit 類別*Builder 結尾的類別,來動態建立出我們的組件。

*Builder 結尾的類別類似觀念,我們前面「」就已經談過,觀念一樣,我就不在重覆。你要建立什麼就是 ...Builder 類別。記的要先Imports System.Reflection.Emit。

RunTimeCreateAssembly 副程式

001#Region "執行環境中產生程式碼"
002    ''' <summary>
003    ''' 建立動態組件
004    ''' </summary>
005    Private Sub RunTimeCreateAssembly()
006        ' 1. 建立組件
007        Console.WriteLine("定義組件")
008        Dim tempName As New AssemblyName()
009        tempName.Name = "KKBruceSampleAssembly"
010        tempName.Version = New Version("1.0.0.0")
011 
012        ' 使用 DefineDynamicAssembly 動態產生組件
013        Dim AssemBL As AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(tempName, AssemblyBuilderAccess.RunAndSave)
014 
015        ' 2. 建立模組
016        Console.WriteLine("定義模組")
017        ' DefineDynamicModule 動態產生模組
018        ' 如果要建立一個單一檔案的組件,並將此組件序列化在磁碟之中,最後的Save名稱必須與以下DefineDynamicModule裡名稱相同。
019        Dim ModuleBL As ModuleBuilder = AssemBL.DefineDynamicModule(tempName.Name, tempName.Name & ".dll")
020 
021        ' 3. 建立型別
022        Console.WriteLine("定義型別")
023        ' DefineType 動態產生型別
024        Dim BruceType As TypeBuilder = ModuleBL.DefineType("KKBruceType", TypeAttributes.Class Or TypeAttributes.Public)
025        ' 指定父類別及介面給DefineType
026        'Dim tb As TypeBuilder = mb.DefineType("KKBruceMyNewType", TypeAttributes.Class Or TypeAttributes.Public,
027        '                                      GetType(Hashtable),
028        '                                      New Type() {GetType(IDisposable)})
029 
030 
031        ' 4. 建立成員
032        Console.WriteLine("定義相關成員")
033        ' 4.1 建立建構子
034        Console.WriteLine("     定義建構子")
035        ' MethodAttributes代表建構子的類型
036        'Dim ConstructorBL As ConstructorBuilder = BruceType.DefineDefaultConstructor(MethodAttributes.Public)
037 
038        ' 4.2 利用ConstructorBuilder建立ILGenerator物件
039 
040        ' --- 註:執行會產生錯誤 ---
041        ' --- 註:依MSDN查詢:Runtime 會為預設建構函式產生程式碼。因此,如果嘗試取得 ILGenerator,則會擲回例外狀況。 ---
042 
043        'Console.WriteLine("     建立ILGenerator物件")
044 
045        ' ILGenerator 可產生 Microsoft Intermediate Language (MSIL) 指令。
046        'Dim codeGen As ILGenerator = ConstructorBL.GetILGenerator()
047 
048        ' Emit:多載。放置指令到 Just-In-Time (JIT) 編譯器的 Microsoft Intermediate Language (MSIL) 資料流中。
049        ' OpCodes 類別:提供 Microsoft Intermediate Language (MSIL) 指令的欄位表示,以用於 ILGenerator 類別成員 (例如 Emit) 的發出。
050        ' Ret欄位:從目前方法傳回,將被呼叫端評估堆疊的傳回值 (如果有的話) 推入至呼叫端的評估堆疊。
051        'codeGen.Emit(OpCodes.Ret)
052 
053        ' 4.3 建立方法
054        Console.WriteLine("     定義方法")
055        Dim MethodBLOne As MethodBuilder = BruceType.DefineMethod("AddStringOne", MethodAttributes.Public, Nothing, New Type() {GetType(String)})
056        ' 第三個參數:建新新方法的回傳型別
057        ' 第四個參數:參數型別
058        Dim MethodBLTwo As MethodBuilder = BruceType.DefineMethod("AddStringTwo", MethodAttributes.Public Or MethodAttributes.Static,
059                                                     Nothing,
060                                                     New Type() {GetType(String)})
061 
062        ' 4.4 建立私有欄位
063        Console.WriteLine("     定義私有欄位")
064        Dim FieldBL As FieldBuilder = BruceType.DefineField("_count", GetType(Int32), FieldAttributes.Private)
065 
066        ' 4.5 建立屬性
067        Console.WriteLine("     定義屬性")
068        ' PropertyAttributes.None 列舉常數不能定義所有的屬性
069        Dim PropertyBL As PropertyBuilder = BruceType.DefineProperty("Count", PropertyAttributes.None, GetType(Int32), Type.EmptyTypes)
070 
071        ' 4.6 在方法中定義設得或設定屬性值
072        Console.WriteLine("     定義方法中設得或設定屬性值")
073        ' 注意,使用MethodAttributes
074        Dim getMethodAttributes As MethodAttributes = MethodAttributes.Public Or
075                                                MethodAttributes.SpecialName Or
076                                                MethodAttributes.HideBySig
077 
078        Dim propGet As MethodBuilder = BruceType.DefineMethod("get_Count", getMethodAttributes, GetType(Int32), Type.EmptyTypes)
079        ' 讓方法可以設定與取得屬性值
080        PropertyBL.SetGetMethod(propGet)
081 
082        ' 5. 建立型別
083        Console.WriteLine("產生新型別")
084        ' --- 註:建立型別之前要為所有 MethodBuilder 執行 GetILGenerator() ---
085        ' --- 註:不然會產生一個「方法 'xxx' 沒有方法主體。」的錯誤,這裡 Debug 花了我好久!XD ---
087        Console.WriteLine(" 產生所有方法的ILGenerator")
088        RunMethodBLGetILGenerator(MethodBLOne)
089        RunMethodBLGetILGenerator(MethodBLTwo)
090        RunMethodBLGetILGenerator(propGet)
091 
092        Console.Write(" 產生型別")
093        Dim KKBruceType As Type = BruceType.CreateType()
094 
095        ' 5.1 顯示此型別方法
096        Console.WriteLine()
097        Console.WriteLine("     顯示型別方法(注意我們產生動態產生的方法)")
098        GetRunTimeAssemblyMethods(KKBruceType)
099        Console.WriteLine()
100        Console.ReadLine()
101 
102        ' 6. 儲存組件到磁碟中
103        Console.WriteLine("儲存組件到磁碟中...")
104        ' 一但被寫入到磁碟中,任何程式都可以載入這個組件使用。
105        AssemBL.Save(tempName.Name & ".dll")
106        Console.WriteLine("儲存完成。(請參考專案目錄下「\bin\Debug」產生的動態組件 " & tempName.Name & ".dll")
107        Console.ReadLine()
108    End Sub
109 
110    ''' <summary>
111    ''' 傳回這個方法的 ILGenerator
112    ''' </summary>
113    ''' <param name="methodInstance">MethodBuilder 的執行個體</param>
114    ''' <remarks></remarks>
115    Private Sub RunMethodBLGetILGenerator(methodInstance As MethodBuilder)
116        ' MethodBuilder.GetILGenerator()
117        ' 傳回這個方法的 ILGenerator,使用預設 Microsoft Intermediate Language (MSIL) 資料流的 64 位元大小。
118        Dim ilgen As ILGenerator = methodInstance.GetILGenerator()
119        ilgen.Emit(OpCodes.Ldnull)
120        ilgen.Emit(OpCodes.Ret)
121    End Sub
122 
123    ''' <summary>
124    ''' 取得動態組件方法屬性
125    ''' </summary>
126    ''' <param name="KKBruceType">型別</param>
127    Private Sub GetRunTimeAssemblyMethods(ByVal KKBruceType As Type)
128        Console.WriteLine("     Full Name: {0}", KKBruceType.FullName)
129        For Each m As MemberInfo In KKBruceType.GetMethods()
130            Console.WriteLine("         Member({0}):{1}", m.MemberType, m.Name)
131        Next
132    End Sub
133#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 檔:

圖一:儲存動態程式碼為組件

參考資料


下載 Reflection (1) ~ (9) 範例程式碼

感謝你們看到這九篇文章,我將第一篇至第九篇的範例整理成一個小小小小的 Console 選擇遊戲,以下是原始碼專案,必須使用 Visual Studio 2010 / .NET Framework 4 才能重新編譯。已編譯執行檔在 \bin\Debug\AssemblyDemo.exe,如果你想試試我前面介紹的 Reflection 工具,也可以從已編譯執行檔裡去看原始碼。



解壓縮密碼:KKBruce

此範例程式碼是「太濕版」一點也不「DRY, 乾」,全部用副程式在呼叫,下一篇我會將把此範例改寫為 OO (物件導向, Object-oriented) 的範例,算是大重構,不過從其中我們也能看到物件導向的優點,很多事你硬要使用「副程式/函式」來做是會…濕透了。

沒有留言:

張貼留言

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。