Mono.Cecil简介与示例


Mono.Cecil - 0.6

项目地址: Mono.Cecil
项目描述: In simple English, with Cecil, you can load existing managed assemblies, browse all the contained types, modify them on the fly and save back to the disk the modified assembly.
类似项目:Microsoft CCI
Common Compiler Infrastructure: Metadata API
Common Compiler Infrastructure: Code Model and AST API
Common Compiler Infrastructure: Sample applications
Common Compiler Infrastructure - Contrib
对比评价:来自 StackOverflow
Mono.Cecil has better, more understandable and easy in use object model. However, I had an ugly bug when used it in my program (reference to the wrong method was saved in the assembly; I think there was some bug with metadata tokens handling)
Microsoft.CCI has an ugly, utterly over-designed object model in the same time lacking many simple features; however, it's more mature than Mono.Cecil. Finally, I abandoned Mono.Cecil and used Microsoft.CCI for my program.

基本示例
Cecil是对已编译生成IL的程序集进行操作,所以先写一个简单的Console exe程序,这里项目名称使用Cecil.Program:
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
35
36
37
38
39
using  System;
using  System.Reflection;
namespace  Cecil.Program
{
     class  Program
     {
         static  void  Main( string [] args)
         {
             TestType tt = new  TestType();
             tt.SayHello();
             tt.AboutMe();
             Console.ReadKey();
         }
     }
     public  class  TestType
     {
         [Obsolete]
         public  void  SayHello()
         {
             Console.WriteLine( "\tHello Cecil !" );
         }
         public  void  AboutMe()
         {
             Type type = typeof (TestType);
             MethodInfo method = type.GetMethod( "SayHello" );
             if  (method.IsVirtual)
                 Console.WriteLine( "\tI'm a virtual method" );
             else
                 Console.WriteLine( "\tI'm a non-virtual method" );
             object [] attributes = method.GetCustomAttributes( false );
             if  (attributes != null  && attributes.Length > 0)
             {
                 Console.WriteLine( "\tI have the following attributes:" );
                 foreach  ( object  attr in  attributes)
                     Console.WriteLine( "\t\t"  + attr.GetType().Name);
             }
         }
     }
}
这个程序集的运行结果如下:
    

方法SayHello的IL代码如下:
    

接下来使用另外一个Console exe程序来修改Cecil.Program.exe,项目名称使用Cecil:
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
35
36
37
38
39
40
41
42
using  Mono.Cecil;
using  Mono.Cecil.Cil;
AssemblyDefinition assembly = AssemblyFactory.GetAssembly( "Cecil.Program.exe" );
TypeDefinition type = assembly.MainModule.Types[ "Cecil.Program.TestType" ];
MethodDefinition sayHello = null ;
foreach  (MethodDefinition md in  type.Methods)
     if  (md.Name == "SayHello" ) sayHello = md;
//Console.WriteLine(string value)方法
MethodInfo writeLine = typeof (Console).GetMethod( "WriteLine"
     , new  Type[] { typeof ( string ) });
//Console.WriteLine方法导入MainModule,并返回在AssemblyDefinition中的引用方式
MethodReference writeLineRef = assembly.MainModule.Import(writeLine);
//在SayHello方法开始位置插入一条trace语句
//  Console.WriteLine(">>Intercepting ");
//如果插入的语句需要使用函数入参,则必须插入在OpCodes.Ldarg等指令之后
CilWorker worker = sayHello.Body.CilWorker;
Instruction ldstr = worker.Create(OpCodes.Ldstr, ">>Intercepting "  + sayHello.Name);
Instruction call = worker.Create(OpCodes.Call, writeLineRef);
Instruction first = sayHello.Body.Instructions[0];
worker.InsertBefore(first, call);
worker.InsertBefore(call, ldstr);
//在SayHello方法结束位置插入一条trace语句
//  Console.WriteLine(">>Intercepted ");
//语句必须插入在OpCodes.Ret指令的前面
int  offset = sayHello.Body.Instructions.Count - 1;
Instruction last = sayHello.Body.Instructions[offset--];
while  (last.OpCode == OpCodes.Nop || last.OpCode == OpCodes.Ret)
     last = sayHello.Body.Instructions[offset--];
ldstr = worker.Create(OpCodes.Ldstr, ">>Intercepted "  + sayHello.Name);
worker.InsertAfter(last, ldstr);
worker.InsertAfter(ldstr, call);
//把SayHello方法改为虚方法
sayHello.IsVirtual = true ;
//给SayHello方法添加一个SerializableAttribute
CustomAttribute attribute = new  CustomAttribute(
     assembly.MainModule.Import(
         typeof (SerializableAttribute).GetConstructor(Type.EmptyTypes)
));
sayHello.CustomAttributes.Add(attribute);
AssemblyFactory.SaveAssembly(assembly, "Cecil.Program.modified.exe" );
Console.WriteLine( "Assembly modified successfully!" );
Console.ReadKey();
编译生成Cecil.exe,然后把Cecil.Program.exe拷贝到这个目录下,运行Cecil.exe,便会在当前目录生成Cecil.Program.modified.exe,运行Cecil.Program.modified.exe结果如下:
    
修改后的方法SayHello的IL代码如下:
    

从上面的基本使用方法可以看出,Cecil的确是易于使用,对象模型结构非常实用,这里是官方网站的一个主要对象结构图:
    

IL指令的复杂性
在assembly、type、method级别上对程序集做修改是非常简单的,但是如果要修改方法体的IL代码,则可能会遇到一些较麻烦的事情,需要细致的处理
例如上面的SayHello方法如果是这样:
1
2
3
4
5
public  void  SayHello( bool  print)
{
     if  (print)
         Console.WriteLine( "\tHello Cecil !" );
}
测试代码这样来调用:
1
2
3
4
TestType2 tt2 = new  TestType2();
tt2.SayHello( true );
tt2.SayHello( false );
Console.ReadKey();
其运行结果只会输出一条Hello Cecil !消息,仍然使用Cecil.exe来修改这个程序集,其运行结果如下图:
    
调用tt2.SayHello(false);时,应该也会有一个>>Intercepted SayHello消息,但是没有输出,对比一下IL代码就清楚了:
    
修改后的IL代码如下:
    
IL_000b那一句,为false时就直接跳转到IL_0021这个返回指令上了,不会输出Intercepted的消息

使用Mono.Cecil也可以修改这个跳转地址,例如:
1
2
3
4
5
//得到指令brfalse.s
Instruction jmp = sayHello.Body.Instructions[1];
....
//把跳转的目标地址改成IL_0017 ldstr指令位置
jmp.Operand = ldstr;
 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM