介绍

这篇距上一篇已经拖3个月之久了,批评自己下。

通过上篇介绍了解如何利用mono反射代码,可以拿出编译好的静态数据、例如方法参数信息之类的。

但实际情况是往往需要的是运行时的数据,就是用户输入等外界的动态数据。

既然是动态的,那就是未知的,怎么通过提前注入的代码获取呢!

阅读目录:

  1. 普通写法
  2. 注入定义
  3. Weave函数
  4. 参数构造
  5. 业务编写
  6. 注入调用

普通写法

 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 = ; 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);
}
}
}

参数构造

动态获取函数参数的函数,代码有详细注释。

    /// <summary>
/// 构建函数参数
/// </summary>
/// <param name="method">要注入的方法</param>
/// <param name="firstInstruction">函数体内第一行指令认 IL_0000: nop</param>
/// <param name="writer">mono IL处理容器</param>
/// <param name="firstArgument">默认第0个参数开始</param>
/// <param name="argumentCount">函数参数的数量,静态数据可以拿到</param>
/// <param name="assembly">要注入的程序集</param>
public static void MakeArrayOfArguments(MethodDefinition method, Instruction firstInstruction, ILProcessor writer, int firstArgument,
int argumentCount, AssemblyDefinition assembly)
{
//实例函数第一个参数值为this(当前实例对象),所以要从1开始。
int thisShift = method.IsStatic ? : ; if (argumentCount > )
{
//我们先创建个和原函数参数,等长的空数组。
writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldc_I4, argumentCount - firstArgument));
//然后实例object数组,赋值给我们创建的数组
writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Newarr,
assembly.MainModule.Import(typeof(object)))); //c#代码描述
//object[] arr=new object[argumentCount - firstArgument]
for (int i = firstArgument; i < argumentCount; i++) //遍历参数
{
var parameter = method.Parameters[i]; //在堆栈上复制一个值
writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Dup));
//将常量 i - firstArgument 进行压栈,数组[i - firstArgument] 这个东东。
writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldc_I4, i - firstArgument));
//将第i + thisShift个参数 压栈。
writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldarg, (short)(i + thisShift)));
//装箱成object
ToObject(assembly, firstInstruction, parameter.ParameterType, writer);
//压栈给数组 arr[i]赋值
writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Stelem_Ref)); //c#代码描述
// arr[i]=value;
}
}
else
{
writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldnull));
}
}
public static void ToObject(AssemblyDefinition assembly, Instruction firstInstruction, TypeReference originalType, ILProcessor writer)
{
if (originalType.IsValueType)
{
//普通值类型进行装箱操作
writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Box, originalType));
}
else
{
if (originalType.IsGenericParameter)
{
//集合装箱
writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Box, assembly.MainModule.Import(originalType)));
} }
}

介绍下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

日志系统实战(二)-AOP动态获取运行时数据的更多相关文章

  1. 使用Mono Cecil 动态获取运行时数据 (Atribute形式 进行注入 用于写Log) [此文报考 xxx is declared in another module and needs to be imported的解决方法]-摘自网络

    目录 一:普通写法 二:注入定义 三:Weave函数 四:参数构造 五:业务编写 六:注入调用 7.  怎么调用别的程序集的方法示例 8. [is declared in another module ...

  2. 《深入理解Java虚拟机》(二)Java虚拟机运行时数据区

    Java虚拟机运行时数据区 详解 2.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第二章 ,为了整理思路,简单记录一下,方便后期查阅. 2.2 运行时数据区域 Java虚拟机 ...

  3. (二)、JAVA运行时数据区域

    根据<Java 虚拟机规范(Java SE 7版)>规定,Java虚拟机所管理的内存,将会包括以下几个运行时数据区域: 注: 1.由所有线程共享的数据区: 对应 java内存模型的主内存, ...

  4. 日志系统实战(一)—AOP静态注入

    背景 近期在写日志系统,需要在运行时在函数内注入日志记录,并附带函数信息,这时就想到用Aop注入的方式. AOP分动态注入和静态注入两种注入的方式. 动态注入方式 利用Remoting的Context ...

  5. java 面向对象(四十一):反射(五)反射应用二:获取运行时类的完整结构

    我们可以通过反射,获取对应的运行时类中所有的属性.方法.构造器.父类.接口.父类的泛型.包.注解.异常等....典型代码: @Test public void test1(){ Class clazz ...

  6. [二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义

    前言简介 class文件是源代码经过编译后的一种平台中立的格式 里面包含了虚拟机运行所需要的所有信息,相当于 JVM的机器语言 JVM全称是Java Virtual Machine  ,既然是虚拟机, ...

  7. 2017.2.21 activiti实战--第十三章--流量数据查询与跟踪(一)查询接口介绍及运行时数据查询

    学习资料:<Activiti实战> 第十三章 流量数据查询与跟踪 本章讲解运行时与历史数据的查询方法.主要包含三种:标准查询,Native查询,CustomSql查询. 13.1 Quer ...

  8. 关于使用动态语言运行时 (. net)

    AutoCAD Managed .NET API允许您使用使用. NET 4.0 引入的动态语言运行时 (DLR). 使用DLR可以直接访问对象, 而无需: 打开一个对象进行读取或写入, 然后在完成后 ...

  9. JVM 运行时数据区(二)

    @ 目录 运行时数据区 共享区 堆区 方法区 隔离区 虚拟机栈 栈帧 本地方法栈 程序计数器 运行时数据区 JVM 运行时数据区主要分为5块 方法区 JDK1.8以后叫做元数据区(Metaspace) ...

随机推荐

  1. 浅谈WEB前后端分离

    重审业务逻辑 用过MVC的童鞋都知道业务逻辑(Bussiness Logic),但是大多对这概念又是模棱两可,业务逻辑从来都是这样难以理解,谈论前后端分离之前这个概念非常有必要探讨一下! 在简单的CR ...

  2. 继承映射关系 TPH、TPT、TPC<EntityFramework6.0>

    每个类型一张表[TPT] 声明方式 public class Business { [Key] public int BusinessId { get; protected set; } public ...

  3. “玲珑杯”ACM比赛 Round #7 B -- Capture(并查集+优先队列)

    题意:初始时有个首都1,有n个操作 +V表示有一个新的城市连接到了V号城市 -V表示V号城市断开了连接,同时V的子城市也会断开连接 每次输出在每次操作后到首都1距离最远的城市编号,多个距离相同输出编号 ...

  4. Linux service命令

    service命令(其实与其说是命令,不如说是脚本),因为service本身就是一个脚本,这个脚本在/sbin路径下,待后续shell脚本功底好了将去认真去看看这个脚本的内容(可不能被人忽悠了.) s ...

  5. Netty参数配置表

  6. C#ListBox对Item进行重绘,包括颜色

    别的不多说了,上代码,直接看 首先设置这行,或者属性窗口设置,这样才可以启动手动绘制,参数有三个 Normal: 自动绘制 OwnerDrawFixed:手动绘制,但间距相同 OwnerDrawVar ...

  7. solr4.5安装配置 linux+tomcat6.0+mmseg4j-1.9.1分词

    首先先介绍下solr的安装配置 solr下载地址 (我这用的solr-4.5.0) 运行环境 JDK 1.5或更高版本 下载地址(Solr 4以上版本,要求JDK 1.6)  我用的JDK1.6 ) ...

  8. WPF menu

    MenuMenu的样式很简单,就是顶部的那个框,如下图 而其中的文字“文件”“图形”...是属于MenuItem的,要灵活使用MenuItem,就需要了解MenuItem.Role的作用 TopLev ...

  9. 简述TCP连接的建立与释放(三次握手、四次挥手)

    在介绍TCP连接的建立与释放之前,先回顾一下相关知识. TCP是面向连接的运输层协议,它提供可靠交付的.全双工的.面向字节流的点对点服务.HTTP协议便是基于TCP协议实现的.(虽然作为应用层协议,H ...

  10. 【BZOJ3036】绿豆蛙的归宿 概率与期望

    最水的概率期望,推荐算法合集之<浅析竞赛中一类数学期望问题的解决方法> #include <iostream> #include <cstdio> using na ...