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

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

一樣,應該要注意的內容,我都註解到程式碼中,與前一篇「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 檔:

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

參考資料


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

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



解壓縮密碼:KKBruce

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

沒有留言:

張貼留言

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