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()
{
// ...
}
}