动态IL织入框架Harmony简单入手
Harmony是一个开放源代码库,旨在在运行时替换、修饰或修改任何现有C#方法。它的主要用在用Mono语言编写的游戏和插件,但是该技术可以与任何.NET版本一起使用。它还照顾对同一方法的多次更改(它们累积而不是覆盖)。
它为每个原始方法创建DynamicMethod方法,并向其织入代码,该代码在开始和结束时调用自定义方法。它还允许您编写过滤器来处理原始的IL代码,从而可以对原始方法进行更详细的操作。
文档可以在这里找到。
- 最新2.0版本终于支持.net core.
- Harmony支持手动(Patch)和自动(PatchAll)织入
- 织入位置可以是执行前(Prefix)、执行后(Postfix)和终结嚣(Finalizer),也可以更详细的手动修改IL(Transpiler)
- 支持构造函数、Getter/Setter、虚/非虚方法、静态方法
手动模式
class NoneGenericClass { private readonly bool _isRunning = true; private int _counter = 1; public int DoSomething() { Console.WriteLine(nameof(DoSomething)); if (_isRunning) { _counter++; } return _counter * 10; } public static int DoSomething2() { Console.WriteLine(nameof(DoSomething2)); return 3333; } public IEnumerable<int> GetNumbers() { Console.WriteLine(nameof(GetNumbers)); yield return 1; yield return 2; yield return 3; } } static class NoneGenericClassPatcher { public static void Patch() { var harmony = new Harmony(nameof(NoneGenericClassPatcher)); harmony.Patch(typeof(NoneGenericClass).GetMethod(nameof(NoneGenericClass.DoSomething)), new HarmonyMethod(GetMethod(nameof(MyPrefix))), new HarmonyMethod(GetMethod(nameof(MyPostfix))), new HarmonyMethod(GetMethod(nameof(MyTranspiler))), new HarmonyMethod(GetMethod(nameof(MyFinalizer)))); Console.WriteLine(new NoneGenericClass().DoSomething()); Console.WriteLine(); harmony.Patch(typeof(NoneGenericClass).GetMethod(nameof(NoneGenericClass.GetNumbers)), new HarmonyMethod(GetMethod(nameof(MyPrefix))), new HarmonyMethod(GetMethod(nameof(PassthroughPostfix))), new HarmonyMethod(GetMethod(nameof(MyTranspiler))), new HarmonyMethod(GetMethod(nameof(MyFinalizer)))); Console.WriteLine(string.Join(", ", new NoneGenericClass().GetNumbers())); Console.WriteLine(); harmony.Patch(typeof(NoneGenericClass).GetMethod(nameof(NoneGenericClass.DoSomething2)), new HarmonyMethod(GetMethod(nameof(StaticPrefix))), new HarmonyMethod(GetMethod(nameof(MyPostfix))), new HarmonyMethod(GetMethod(nameof(MyTranspiler))), new HarmonyMethod(GetMethod(nameof(MyFinalizer)))); Console.WriteLine(NoneGenericClass.DoSomething2()); } static MethodInfo GetMethod(string name) => typeof(NoneGenericClassPatcher).GetMethod(name, BindingFlags.Static | BindingFlags.Public); public static bool MyPrefix(out Stopwatch __state, ref bool ____isRunning) { __state = Stopwatch.StartNew(); Console.WriteLine($"{nameof(MyPrefix)} {____isRunning}"); return true; } public static bool StaticPrefix(out Stopwatch __state) { __state = Stopwatch.StartNew(); Console.WriteLine($"{nameof(StaticPrefix)}"); return true; } public static void MyPostfix(Stopwatch __state, ref int __result, MethodBase __originalMethod) { Console.WriteLine($"{__state.ElapsedMilliseconds} {__result++}"); Console.WriteLine(nameof(MyPostfix)); } public static IEnumerable<int> PassthroughPostfix(IEnumerable<int> values) { yield return 0; foreach (var value in values) if (value > 1) yield return value * 10; yield return 99; Console.WriteLine(nameof(PassthroughPostfix)); } // looks for STDFLD someField and inserts CALL MyExtraMethod before it public static IEnumerable<CodeInstruction> MyTranspiler(IEnumerable<CodeInstruction> instructions) { Console.WriteLine(nameof(MyTranspiler)); //var found = false; foreach (var instruction in instructions) { //if (instruction.opcode == OpCodes.Stfld && instruction.operand == f_someField) //{ // yield return new CodeInstruction(OpCodes.Call, m_MyExtraMethod); // found = true; //} yield return instruction; } //if (found == false) // ReportError("Cannot find <Stdfld someField> in OriginalType.OriginalMethod"); } public static void MyFinalizer(Exception __exception) { Console.WriteLine($"{nameof(MyFinalizer)} {__exception}"); } }
自动模式
public class Annotations { private readonly bool _isRunning; public IEnumerable<int> GetNumbers() { Console.WriteLine(nameof(GetNumbers)); yield return 1; yield return 2; yield return 3; } } [HarmonyPatch(typeof(Annotations))] [HarmonyPatch(nameof(Annotations.GetNumbers))] public class AnnotationsPatcher { static AccessTools.FieldRef<Annotations, bool> isRunningRef = AccessTools.FieldRefAccess<Annotations, bool>("_isRunning"); public static void Patch() { var harmony = new Harmony(nameof(AnnotationsPatcher)); harmony.PatchAll(); Console.WriteLine(string.Join(", ", new Annotations().GetNumbers())); } static bool Prefix(Annotations __instance) { Console.WriteLine("Prefix"); return true; } /// <summary>Not working</summary> static IEnumerable<int> Postfix(IEnumerable<int> values) { yield return 0; foreach (var value in values) if (value > 1) yield return value * 10; yield return 99; Console.WriteLine(nameof(Postfix)); } // looks for STDFLD someField and inserts CALL MyExtraMethod before it public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { Console.WriteLine(nameof(Transpiler)); //var found = false; foreach (var instruction in instructions) { //if (instruction.opcode == OpCodes.Stfld && instruction.operand == f_someField) //{ // yield return new CodeInstruction(OpCodes.Call, m_MyExtraMethod); // found = true; //} yield return instruction; } //if (found == false) // ReportError("Cannot find <Stdfld someField> in OriginalType.OriginalMethod"); } }
运行代码
static void Main(string[] args) { NoneGenericClassPatcher.Patch(); Console.WriteLine(); AnnotationsPatcher.Patch(); }
输出结果
MyTranspiler MyPrefix True DoSomething 1 20 MyPostfix MyFinalizer 21 MyTranspiler MyPrefix True MyFinalizer GetNumbers 1, 2, 3 MyTranspiler StaticPrefix DoSomething2 0 3333 MyPostfix MyFinalizer 3334 Transpiler Prefix GetNumbers Postfix 0, 20, 30, 99