日志系统实战(二)-AOP动态获取运行时数据
介绍
这篇距上一篇已经拖3个月之久了,批评自己下。
通过上篇介绍了解如何利用mono反射代码,可以拿出编译好的静态数据、例如方法参数信息之类的。
但实际情况是往往需要的是运行时的数据,就是用户输入等外界的动态数据。
既然是动态的,那就是未知的,怎么通过提前注入的代码获取呢!
阅读目录:
普通写法
1 2 3 4 | 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函数
这块代码在上篇已经有过注释了,这里不在多做描述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 之前追加。 自上而下
业务编写
定义个要注入的用户类,然后标记下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [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; } } |
平常的业务写法,不需要增加多余的代码。
1 2 3 4 5 6 7 8 9 | 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
如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~