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

时间:2024-04-28 17:36:54

目录

一:普通写法

二:注入定义

三:Weave函数

四:参数构造

五:业务编写

六:注入调用

7.  怎么调用别的程序集的方法示例

8. [is declared in another module and needs to be imported的解决方法:

一:普通写法

1
2
3
4
public static string GetPoint(int x, int y)
 {
    var value=x;
}

哇 好简单啊。其实动态获取和我们普通这样写代码是一样的,我们把要注入的代码,生成一个接收的变量就可以了。 

就像上面value 一样接收,然后传递给我们自己函数就可以了。

二 :注入定义

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

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

四 :参数构造

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

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

介绍下mono InsertBefore这个函数。 这个函数是在某个指令之前插入指令。来张图

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

通过图我们看出,第一行指令是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盘,用我们前面的Weave函数进行注入。

  CodeInject.Weave(new string[] { @"D:\test\Test.exe" });

运行结果如下

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

反编译后的c#

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

-------------------------------------------------------------------------------------------------------------------------

7. 以下这段是怎么调用别的程序集的方法示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Mono.Cecil.Cil; namespace BlogSample
{
class Program
{
static void Main(string[] args)
{
AssemblyDefinition assembiy = AssemblyFactory.GetAssembly(args[0]);
foreach (Mono.Cecil.TypeDefinition item in assembiy.MainModule.Types)
{
foreach (MethodDefinition method in item.Methods)
{
if (method.Name.Equals("Main"))
{ var ins = method.Body.Instructions[0];
var worker = method.Body.CilWorker; 
worker.InsertBefore(ins, worker.Create(OpCodes.Ldstr, "Method start…")); //定义一个变量
worker.InsertBefore(ins, worker.Create(OpCodes.Call,
assembiy.MainModule.Import(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) })))); //调用Console.WriteLine() 方法
ins = method.Body.Instructions[method.Body.Instructions.Count - 1]; worker.InsertBefore(ins, worker.Create(OpCodes.Ldstr, "Method finish…"));
worker.InsertBefore(ins, worker.Create(OpCodes.Call,
assembiy.MainModule.Import(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }))));
break;
}
} } AssemblyFactory.SaveAssembly(assembiy, "IL_" + args[0]);
Console.Read();
}
}
}

8. [is declared in another module and needs to be imported的解决方法:

报错:

C:\dev\MongoConnect\WeavingTaskTest\Weaving\CodeWeaving.targets(32,5): error MSB4018: System.ArgumentException: Member 'System.Void MongoConnect.BaseDataObject::SetProperty(System.String,System.Object)' is declared in another module and needs to be imported

解决方法: 就是把你要调用的方法 和你调用的方法放到一个一个程序集下可以解决这个问题

解决方法原文:

4down voteaccepted

I've found the solution. The reason was really funny.

Module.Import() method must be called from current module we want to modify, not the module where method is defined. It is not clear from original docs.

For example, we want to add some method defined in the Referenced.dll assembly to our Main.dll assembly. Then we have to find main module of our Main.dll assembly and then call MainModule.Import(methodFromReferencedAssembly);