Use Mono Cecil to do IL Injection

Use Mono Cecil to do IL Injection


自从知道 PostSharp 这套有趣的 AOP 组件后,就一直对其中的原理感到好奇。根据官方博客,其中所使用的一项技术,就是 IL Injection,所以我尝试寻找可行的作法。

.Net Framework 其实有提供相关的功能(ILGenerator),可参考下列网址:

http://msdn.microsoft.com/zh-tw/library/system.reflection.emit(v=vs.95).aspx

但使用 ILGenerator 会有些不方便,因为得对 IL 有一定的了解才有办法实践。于是乎我开始寻找相关的工作来用,之后找到了 Cecil。它是 Mono 底下的项目,将一些 Reflection 相关的 API 包装起来,比直接使用 Reflection API 方便不少。

而底下的范例,将会示范如何将某个类底下的方法塞进另一个类底下的方法,首先声明 TestClass 以及 TestClass2。


using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestClassLibrary
{
    public class TestClass
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }

        public void TestMethod()
        {
            string result = string.Empty;
            result = this.GetType().Name;
            Console.WriteLine(result);
        }

    }

    public class TestClass2
    {
        public void TestMethod1()
        {
            Console.WriteLine("1");
            Console.WriteLine("2");
            Console.WriteLine("3");
        }
    }
}

接下来将 TestClass2 底下的 TestMethod1 插进 TestClass 底下的 TestMethod。


            AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(pathBin);

            TypeDefinition TestClassType = assembly.MainModule.GetType("TestClassLibrary.TestClass");
            TypeDefinition TestClassType2 = assembly.MainModule.GetType("TestClassLibrary.TestClass2");
            MethodDefinition method1 = TestClassType.Methods.Where(m => m.Name == "TestMethod").First();
            MethodDefinition method2 = TestClassType2.Methods.Where(m => m.Name == "TestMethod1").First();
            ILProcessor ilProcessor = method1.Body.GetILProcessor();
            Instruction inst = method1.Body.Instructions[0];
           
            foreach (Instruction instruction in method2.Body.Instructions)
            {
                ilProcessor.Append(instruction);
            }

            assembly.Write("TestClassLibrary.dll");

之后我另开一个项目,引用 TestClassLibray.dll 组件,但 TestMethod 的执行结果没任何改变,于是我直接检视 IL code,发现我忽略一个很重要的事情…

2013-06-18_232719

ret 命令就相当于源代码里的 return ,这东西插在源代码里,后面的逻辑当然都不会执行到。之后就改变一下程序的写法。


            {
                if (instruction.OpCode.Code != Code.Ret)
                    ilProcessor.InsertBefore(inst, instruction);
            }

避开 ret 命令后,就达到我要的效果了。

参考数据

http://www.mono-project.com/Cecil:FAQ

Cecil 下载位址

https://github.com/jbevain/cecil