zoukankan      html  css  js  c++  java
  • 动态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
  • 相关阅读:
    Windows下Subversion配置管理员指南
    asp.net HttpHand和HttpModule的详细解释,包括Asp.Net对Http请求的处理流程。
    SQL Server 2000不能远程连接的问题
    ASP通用分页类[强]
    不错的站长工具网址(不断收集中....)
    函数的四种调用模式
    HDOJ 3943 Kth Nya Number(数位DP)
    HDOJ 2196 Computer
    HDOJ 3695 Computer Virus on Planet Pandora (AC自动机)
    HDOJ 4277 USACO ORZ (搜索+剪枝)
  • 原文地址:https://www.cnblogs.com/qhca/p/12336332.html
Copyright © 2011-2022 走看看