一樣,應該要注意的內容,我都註解到程式碼中,與前一篇「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 --- |
086 | ' --- 註:參考:http://msdn.microsoft.com/zh-tw/library/system.reflection.emit.methodbuilder.definegenericparameters%28VS.85%29.aspx 範例才找出解法 --- |
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 檔:
![]() |
圖一:儲存動態程式碼為組件 |
參考資料
- 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) 的範例,算是大重構,不過從其中我們也能看到物件導向的優點,很多事你硬要使用「副程式/函式」來做是會…濕透了。
沒有留言:
張貼留言
感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。