博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
日志系统实战(二)-AOP动态获取运行时数据
阅读量:5885 次
发布时间:2019-06-19

本文共 6537 字,大约阅读时间需要 21 分钟。

介绍

这篇距上一篇已经拖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    ///  2         /// 构建函数参数 3         ///  4         /// 要注入的方法 5         /// 函数体内第一行指令认 IL_0000: nop 6         /// mono IL处理容器 7         /// 默认第0个参数开始 8         /// 函数参数的数量,静态数据可以拿到 9         /// 要注入的程序集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                     //装箱成object37                     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             else46             {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             else58             {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

转载地址:http://uslix.baihongyu.com/

你可能感兴趣的文章
Windows phone8 基础篇(三) 常用控件开发
查看>>
Oracle学习笔记之五,Oracle 11g的PL/SQL入门
查看>>
大叔手记(3):Windows Silverlight/Phone7/Mango开发学习系列教程
查看>>
考拉消息中心消息盒子处理重构(策略模式)
查看>>
so easy 前端实现多语言
查看>>
【追光者系列】HikariCP源码分析之ConcurrentBag&J.U.C SynchronousQueue、CopyOnWriteArrayList...
查看>>
canvas系列教程05-柱状图项目3
查看>>
css绘制几何图形
查看>>
HTML标签
查看>>
理解JS中的Event Loop机制
查看>>
转载:字符编码笔记:ASCII,Unicode和UTF 8
查看>>
修复看不懂的 Console Log
查看>>
Android跨进程通信 AIDL使用
查看>>
ajax常见面试题
查看>>
结合kmp算法的匹配动画浅析其基本思想
查看>>
vue进行wepack打包执行npm run build出现错误
查看>>
【d3.js v4基础】过渡transition
查看>>
VUEJS开发规范
查看>>
Android系统的创世之初以及Activity的生命周期
查看>>
人人都会数据采集- Scrapy 爬虫框架入门
查看>>