Dynamically replace the contents of a C# method? Ask Question

Dynamically replace the contents of a C# method? Ask Question

What I want to do is change how a C# method executes when it is called, so that I can write something like this:

[Distributed]
public DTask<bool> Solve(int n, DEvent<bool> callback)
{
    for (int m = 2; m < n - 1; m += 1)
        if (m % n == 0)
            return false;
    return true;
}

At run-time, I need to be able to analyse methods that have the Distributed attribute (which I already can do) and then insert code before the body of the function executes and after the function returns. More importantly, I need to be able to do it without modifying code where Solve is called or at the start of the function (at compile time; doing so at run-time is the objective).

At the moment I have attempted this bit of code (assume t is the type that Solve is stored in, and m is a MethodInfo of Solve):

private void WrapMethod(Type t, MethodInfo m)
{
    // Generate ILasm for delegate.
    byte[] il = typeof(Dpm).GetMethod("ReplacedSolve").GetMethodBody().GetILAsByteArray();

    // Pin the bytes in the garbage collection.
    GCHandle h = GCHandle.Alloc((object)il, GCHandleType.Pinned);
    IntPtr addr = h.AddrOfPinnedObject();
    int size = il.Length;

    // Swap the method.
    MethodRental.SwapMethodBody(t, m.MetadataToken, addr, size, MethodRental.JitImmediate);
}

public DTask<bool> ReplacedSolve(int n, DEvent<bool> callback)
{
    Console.WriteLine("This was executed instead!");
    return true;
}

ただし、MethodRental.SwapMethodBody は動的モジュールでのみ機能し、既にコンパイルされてアセンブリに格納されているモジュールでは機能しません。

そこで、SwapMethodBodyを効果的に実行する方法を探しています。読み込まれ実行中のアセンブリに既に格納されているメソッド

注意: メソッドを動的モジュールに完全にコピーする必要がある場合は問題になりませんが、この場合は、IL 全体にコピーする方法を見つけるとともに、Solve() へのすべての呼び出しを更新して、新しいコピーを指すようにする必要があります。

ベストアンサー1

開示: Harmony は、この投稿の著者である私によって作成され、管理されているライブラリです。

ハーモニー2は、実行時に既存のあらゆる種類の C# メソッドを置き換え、装飾、または変更するように設計されたオープン ソース ライブラリ (MIT ライセンス) です。主な対象は、Mono または .NET で記述されたゲームとプラグインです。同じメソッドに対する複数の変更を処理し、変更が互いに上書きされるのではなく蓄積されます。

すべてのオリジナル メソッドに対して動的な置換メソッドを作成し、開始時と終了時にカスタム メソッドを呼び出すコードをそれらに出力します。また、オリジナルの IL コードを処理するフィルターや、オリジナル メソッドをより詳細に操作できるカスタム例外ハンドラーを記述することもできます。

プロセスを完了するには、動的メソッドのコンパイルから生成されたアセンブラを指す、元のメソッドのトランポリンに単純なアセンブラ ジャンプを書き込みます。これは、Windows、macOS、および Mono がサポートするすべての Linux の 32/64 ビットで機能します。

ドキュメントは以下にありますここ

ソース

オリジナルコード

public class SomeGameClass
{
    private bool isRunning;
    private int counter;

    private int DoSomething()
    {
        if (isRunning)
        {
            counter++;
            return counter * 10;
        }
    }
}

Harmony アノテーションによるパッチング

using SomeGame;
using HarmonyLib;

public class MyPatcher
{
    // make sure DoPatching() is called at start either by
    // the mod loader or by your injector

    public static void DoPatching()
    {
        var harmony = new Harmony("com.example.patch");
        harmony.PatchAll();
    }
}

[HarmonyPatch(typeof(SomeGameClass))]
[HarmonyPatch("DoSomething")]
class Patch01
{
    static FieldRef<SomeGameClass,bool> isRunningRef =
        AccessTools.FieldRefAccess<SomeGameClass, bool>("isRunning");

    static bool Prefix(SomeGameClass __instance, ref int ___counter)
    {
        isRunningRef(__instance) = true;
        if (___counter > 100)
            return false;
        ___counter = 0;
        return true;
    }

    static void Postfix(ref int __result)
    {
        __result *= 2;
    }
}

あるいは、リフレクションによる手動パッチ

using SomeGame;
using System.Reflection;
using HarmonyLib;

public class MyPatcher
{
    // make sure DoPatching() is called at start either by
    // the mod loader or by your injector

    public static void DoPatching()
    {
        var harmony = new Harmony("com.example.patch");

        var mOriginal = typeof(SomeGameClass).GetMethod("DoSomething", BindingFlags.Instance | BindingFlags.NonPublic);
        var mPrefix = typeof(MyPatcher).GetMethod("MyPrefix", BindingFlags.Static | BindingFlags.Public);
        var mPostfix = typeof(MyPatcher).GetMethod("MyPostfix", BindingFlags.Static | BindingFlags.Public);
        // add null checks here

        harmony.Patch(mOriginal, new HarmonyMethod(mPrefix), new HarmonyMethod(mPostfix));
    }

    public static void MyPrefix()
    {
        // ...
    }

    public static void MyPostfix()
    {
        // ...
    }
}

おすすめ記事