動態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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM