介紹
這篇距上一篇已經拖3個月之久了,批評自己下。
通過上篇介紹瞭解如何利用mono反射程式碼,可以拿出編譯好的靜態資料、例如方法引數資訊之類的。
但實際情況是往往需要的是執行時的資料,就是使用者輸入等外界的動態資料。
既然是動態的,那就是未知的,怎麼通過提前注入的程式碼獲取呢!
閱讀目錄:
普通寫法
public static string GetPoint(int x, int y) { var value=x; }
動態獲取和普通這樣寫程式碼是一樣的,只需要把注入的程式碼,生成一個同樣的接收變數就可以了。
就像上面value 一樣接收,然後傳遞給記錄的函式就可以了。
注入定義
public class WeaveService : Attribute { } public class WeaveAction : Attribute { } public class Log : WeaveAction { public static void OnActionBefore(MethodBase mbBase, object[] args) { for (int i = 0; i < args.Length; i++) { Console.WriteLine(string.Format("{0}方法,第{1}引數是:{2}",mbBase.Name,i, args[i])); } } }
WeaveService WeaveAction 2個Attribute是注入的標記,方便在注入查詢快速定位。
OnActionBefore是接收函式,arg就是函式執行時的引數。
Weave函式
這塊程式碼在上篇已經有過註釋了,這裡不在多做描述。
public static void Weave(string[] assemblyPath) { foreach (var item in assemblyPath) { var assembly = AssemblyDefinition.ReadAssembly(item); var types = assembly.MainModule.Types.Where(n => n.CustomAttributes.Any(y => y.AttributeType.Resolve().Name == "WeaveService")); foreach (var type in types) { foreach (var method in type.Methods) { var attrs = method.CustomAttributes.Where(y => y.AttributeType.Resolve().BaseType.Name == "WeaveAction"); foreach (var attr in attrs) { var resolve = attr.AttributeType.Resolve(); var ilProcessor = method.Body.GetILProcessor(); var firstInstruction = ilProcessor.Body.Instructions.First(); var onActionBefore = resolve.GetMethods().Single(n => n.Name == "OnActionBefore"); var mfReference = assembly.MainModule.Import(typeof(System.Reflection.MethodBase).GetMethod("GetCurrentMethod")); ilProcessor.InsertBefore(firstInstruction, ilProcessor.Create(OpCodes.Call, mfReference)); MakeArrayOfArguments(method, firstInstruction, ilProcessor, 0, method.Parameters.Count, assembly); ilProcessor.InsertBefore(firstInstruction, ilProcessor.Create(OpCodes.Call, onActionBefore)); } } } if (types.Any()) { assembly.Write(item); } } }
引數構造
動態獲取函式引數的函式,程式碼有詳細註釋。
1 /// <summary> 2 /// 構建函式引數 3 /// </summary> 4 /// <param name="method">要注入的方法</param> 5 /// <param name="firstInstruction">函式體內第一行指令認 IL_0000: nop</param> 6 /// <param name="writer">mono IL處理容器</param> 7 /// <param name="firstArgument">預設第0個引數開始</param> 8 /// <param name="argumentCount">函式引數的數量,靜態資料可以拿到</param> 9 /// <param name="assembly">要注入的程式集</param> 10 public static void MakeArrayOfArguments(MethodDefinition method, Instruction firstInstruction, ILProcessor writer, int firstArgument, 11 int argumentCount, AssemblyDefinition assembly) 12 { 13 //例項函式第一個引數值為this(當前例項物件),所以要從1開始。 14 int thisShift = method.IsStatic ? 0 : 1; 15 16 if (argumentCount > 0) 17 { 18 //我們先建立個和原函式引數,等長的空陣列。 19 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldc_I4, argumentCount - firstArgument)); 20 //然後例項object陣列,賦值給我們建立的陣列 21 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Newarr, 22 assembly.MainModule.Import(typeof(object)))); 23 24 //c#程式碼描述 25 //object[] arr=new object[argumentCount - firstArgument] 26 for (int i = firstArgument; i < argumentCount; i++) //遍歷引數 27 { 28 var parameter = method.Parameters[i]; 29 30 //在堆疊上覆制一個值 31 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Dup)); 32 //將常量 i - firstArgument 進行壓棧,陣列[i - firstArgument] 這個東東。 33 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldc_I4, i - firstArgument)); 34 //將第i + thisShift個引數 壓棧。 35 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldarg, (short)(i + thisShift))); 36 //裝箱成object 37 ToObject(assembly, firstInstruction, parameter.ParameterType, writer); 38 //壓棧給陣列 arr[i]賦值 39 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Stelem_Ref)); 40 41 //c#程式碼描述 42 // arr[i]=value; 43 } 44 } 45 else 46 { 47 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldnull)); 48 } 49 } 50 public static void ToObject(AssemblyDefinition assembly, Instruction firstInstruction, TypeReference originalType, ILProcessor writer) 51 { 52 if (originalType.IsValueType) 53 { 54 //普通值型別進行裝箱操作 55 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Box, originalType)); 56 } 57 else 58 { 59 if (originalType.IsGenericParameter) 60 { 61 //集合裝箱 62 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Box, assembly.MainModule.Import(originalType))); 63 } 64 65 } 66 }
介紹下mono InsertBefore這個函式,這個函式是在某個指令之前插入指令。
通過上圖看出,第一行指令是IL_0000: nop 。 第一行追加了 ldc.i4 2 指令,第二行我們還是nop 之前追加。 自上而下
業務編寫
定義個要注入的使用者類,然後標記下。
[WeaveService] public static class UserManager { [Log] public static string GetUserName(int userId, string memberid) { return "成功"; } [Log] public static string GetPoint(int x, int y) { var sum = x + y; return "使用者積分: " + sum; } }
平常的業務寫法,不需要增加多餘的程式碼。
public static void Main(string[] args) { UserManager.GetUserName(1,"v123465"); UserManager.GetPoint(2, 3); Console.ReadLine(); }
注入呼叫
把業務類編譯輸入到D盤test目錄下,用前面的Weave函式對Test.exe進行注入,即分析Test.exe編譯生成的IL程式碼,新增額外的程式碼段。
CodeInject.Weave(new string[] { @"D:\test\Test.exe" });
執行結果如下
反編譯後的c#
總結
通過靜態注入,能使我們更好的從實際用途上去了解IL語言。
拿到動態資料僅僅拋磚引玉,利用Mono可以寫自己的AOP靜態元件。
參考資源
postsharp原始碼
http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes_fields(v=vs.110).aspx