一、什麼是代碼注入
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));
}
}