自從知道 PostSharp 這套有趣的 AOP 元件後,就一直對其中的原理感到好奇。根據官方部落格,其中所使用的一項技術,就是 IL Injection,所以我嚐試尋找可行的作法。
.Net Framework 其實有提供相關的功能(ILGenerator),可參考下列網址:
http://msdn.microsoft.com/zh-tw/library/system.reflection.emit(v=vs.95).aspx
但使用 ILGenerator 會有些不方便,因為得對 IL 有一定的了解才有辦法實作。於是乎我開始尋找相關的工作來用,之後找到了 Cecil。它是 Mono 底下的專案,將一些 Reflection 相關的 API 包裝起來,比直接使用 Reflection API 方便不少。
而底下的範例,將會示範如何將某個類別底下的方法塞進另一個類別底下的方法,首先宣告 TestClass 以及 TestClass2。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestClassLibrary { public class TestClass { public string Field1 { get; set; } public string Field2 { get; set; } public void TestMethod() { string result = string.Empty; result = this.GetType().Name; Console.WriteLine(result); } } public class TestClass2 { public void TestMethod1() { Console.WriteLine("1"); Console.WriteLine("2"); Console.WriteLine("3"); } } }
接下來將 TestClass2 底下的 TestMethod1 插進 TestClass 底下的 TestMethod。
string pathBin = "TestClassLibrary.dll"; AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(pathBin); TypeDefinition TestClassType = assembly.MainModule.GetType("TestClassLibrary.TestClass"); TypeDefinition TestClassType2 = assembly.MainModule.GetType("TestClassLibrary.TestClass2"); MethodDefinition method1 = TestClassType.Methods.Where(m => m.Name == "TestMethod").First(); MethodDefinition method2 = TestClassType2.Methods.Where(m => m.Name == "TestMethod1").First(); ILProcessor ilProcessor = method1.Body.GetILProcessor(); Instruction inst = method1.Body.Instructions[0]; foreach (Instruction instruction in method2.Body.Instructions) { ilProcessor.Append(instruction); } assembly.Write("TestClassLibrary.dll");
之後我另開一個專案,引用 TestClassLibray.dll 元件,但 TestMethod 的執行結果沒任何改變,於是我直接檢視 IL code,發現我忽略一個很重要的事情…
ret 指令就相當於原始碼裡的 return ,這東西插在原始碼裡,後面的邏輯當然都不會執行到。之後就改變一下程式的寫法。
foreach (Instruction instruction in method2.Body.Instructions) { if (instruction.OpCode.Code != Code.Ret) ilProcessor.InsertBefore(inst, instruction); }
避開 ret 指令後,就達到我要的效果了。
參考資料
http://www.mono-project.com/Cecil:FAQ
Cecil 下載位址