一、什么是代码注入
C#程序编译生成中间代码IL,为了实现问题修复和一些通用功能扩展,通常所使用的就是代码注入。
xlua的热修复方案即采用了代码注入的方式,没有污染C#代码,也不需要提前埋点,十分方便。
再比如这篇文章讲述了使用代码注入来做一些工具,:自动注入代码统计每个函数的执行效率以及内存分配
方案
代码注入需要借助一个注入工具,就是Mono.Cecil开发包,它可以让我们对编译好的DLL程序集进行IL代码注入。
http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/
当然,我们也可以在Unity安装目录下,通话在Unity\Editor\Data\Managed下找到Mono.Cecil.dll、Mono.Cecil.Mdb.dll、Mono.Cecil.Pdb.dll其放在Assets/Plugins/Cecil目录中;(Plugins文件夹是专门用来方式Native插件的,会被自动Build);
扩展(什么是DLL)
动态链接库是微软公司在windows操作系统中实现共享函数库概念的一种方式,所谓动态链接就是把一些经常会共享的代码制作成DLL文件,当可执行文件调用到DLL文件时,操作系统才会将DLL加载到内存中,也就是按需加载,能够减少内存浪费。
DLL的最初目的是为了节约应用程序所需的磁盘和内存空间,通过将许多应用共享的代码切分到同一个DLL中,在硬盘上存为一个文件,在内存中使用一个实例。
二、实际案例
这个案例主要参考上面这位同学的,主要内容是写了两个用于获取最大值的方法,其中一个方法是错误的,然后通过代码注入进行修复。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class TestInjectAttribute : Attribute {
}
public class Normal {
public static int Max(int a, int b) {
Debug.LogFormat("a = {0}, b = {1}", a, b);
return a > b ? a : b;
}
}
[TestInject]
public class Inject {
public static int Max(int a, int b) {
return a;
}
}
public class ILTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Debug.LogFormat("Normal Max: {0}", Normal.Max(3, 5));
Debug.LogFormat("Inject Max: {0}", Inject.Max(3, 5));
}
}
在实现代码注入中,尤其是在InjectMethod中真正做代码注入工作的地方,这里需要使用到CIL的中间语言指令,维基百科中给出了所有命令的列表,命令都是根据其含义进行的缩写,比较容易理解:维基百科-List of CIL instructions
同时如果需要了解CIL,了解C#代码和IL中间代码的转换,可以参考:维基百科-Common Intermediate Language
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
using UnityEditor;
using System.Linq;
public static class InjectTool
{
public const string AssemblyPath = "./Library/ScriptAssemblies/Assembly-CSharp.dll";
[MenuItem("CustomTools/Inject")]
public static void Inject() {
Debug.Log("Inject Start");
if (Application.isPlaying || EditorApplication.isCompiling) {
Debug.Log("Failed: Play Mode or Is Compliling");
return;
}
var readerParameters = new ReaderParameters { ReadSymbols = true };
var assembly = AssemblyDefinition.ReadAssembly(AssemblyPath, readerParameters);
if (assembly == null) {
Debug.LogError(string.Format("Inject Load Assembly Failed: {0}", AssemblyPath));
return;
}
try
{
var module = assembly.MainModule;
foreach (var type in module.Types)
{
var needInjectAttr = typeof(TestInjectAttribute).FullName;
bool needInject = type.CustomAttributes.Any(typeAttribute => typeAttribute.AttributeType.FullName == needInjectAttr);
if (!needInject)
{
continue;
}
foreach (var method in type.Methods)
{
if (method.IsConstructor || method.IsGetter || method.IsSetter || !method.IsPublic)
{
continue;
}
InjectMethod(module, method);
}
}
assembly.Write(AssemblyPath, new WriterParameters { WriteSymbols = true });
}
catch (Exception ex)
{
Debug.LogError(string.Format("InjectTool Inject failed: {0}", ex));
throw;
}
finally {
if (assembly.MainModule.SymbolReader != null) {
Debug.Log("InjectTool inject SymbolReader.Dispose success");
assembly.MainModule.SymbolReader.Dispose();
}
}
Debug.Log("Inject End");
}
private static void InjectMethod(ModuleDefinition module, MethodDefinition method) {
var objType = module.ImportReference(typeof(System.Object));
var intType = module.ImportReference(typeof(System.Int32));
var logFormatMethod = module.ImportReference(typeof(Debug).GetMethod("LogFormat", new[] {typeof(string), typeof(object[]) }));
// 开始注入代码
var insertPoint = method.Body.Instructions[0];
var ilProcessor = method.Body.GetILProcessor();
// 设置一些标签用于语句跳转
var label1 = ilProcessor.Create(OpCodes.Ldarg_1);
var label2 = ilProcessor.Create(OpCodes.Stloc_0);
var label3 = ilProcessor.Create(OpCodes.Ldloc_0);
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Nop));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ldstr, "a = {0}, b = {1}"));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ldc_I4_2));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Newarr, objType));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Dup));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ldc_I4_0));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ldarg_0));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Box, intType));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Stelem_Ref));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Dup));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ldc_I4_1));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ldarg_1));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Box, intType));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Stelem_Ref));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Call, logFormatMethod));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ldarg_0));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ldarg_1));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ble, label1));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ldarg_0));
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Br, label2));
ilProcessor.InsertBefore(insertPoint, label1);
ilProcessor.InsertBefore(insertPoint, label2);
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Br, label3));
ilProcessor.InsertBefore(insertPoint, label3);
ilProcessor.InsertBefore(insertPoint, ilProcessor.Create(OpCodes.Ret));
}
}