问题描述
我想要做的是改变调用 C# 方法时的执行方式,以便我可以编写如下内容:
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;
}
在运行时,我需要能够分析具有 Distributed 属性的方法(我已经可以做到),然后在函数体执行之前和函数返回之后插入代码.更重要的是,我需要能够在不修改调用 Solve 的地方或在函数开始时修改代码的情况下(在编译时;在运行时这样做是目标).
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).
目前我已经尝试了这段代码(假设 t 是存储 Solve 的类型,而 m 是 Solve 的 MethodInfo):
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 只适用于动态模块;不是那些已经编译并存储在程序集中的.
However, MethodRental.SwapMethodBody only works on dynamic modules; not those that have already been compiled and stored in the assembly.
因此,我正在寻找一种方法来有效地在 方法上执行 SwapMethodBody,该方法已经存储在已加载和正在执行的程序集中.
So I'm looking for a way to effectively do SwapMethodBody on a method that is already stored in a loaded and executing assembly.
注意,如果我必须将方法完全复制到动态模块中,这不是问题,但在这种情况下,我需要找到一种方法来复制整个 IL 以及更新对 Solve() 的所有调用这样他们就会指向新的副本.
Note, it is not an issue if I have to completely copy the method into a dynamic module, but in this case I need to find a way to copy across the IL as well as update all of the calls to Solve() such that they would point to the new copy.
推荐答案
Harmony 2 是一个开源库(MIT 许可),旨在替换、修饰或修改现有的 C#运行时的任何类型的方法.它的主要重点是用 Mono 或 .NET 编写的游戏和插件.它负责处理对同一方法的多次更改 - 它们会累积而不是相互覆盖.
Harmony 2 is an open source library (MIT license) designed to replace, decorate or modify existing C# methods of any kind during runtime. It main focus is games and plugins written in Mono or .NET. It takes care of multiple changes to the same method - they accumulate instead of overwrite each other.
它为每个原始方法创建动态替换方法,并向它们发出在开始和结束时调用自定义方法的代码.它还允许您编写过滤器来处理原始 IL 代码和自定义异常处理程序,以便对原始方法进行更详细的操作.
It creates dynamic replacement methods for every original method and emits code to them that calls custom methods at the start and end. It also allows you to write filters to process the original IL code and custom exception handlers which allows for more detailed manipulation of the original method.
为了完成这个过程,它在原始方法的trampoline中写了一个简单的汇编程序跳转,指向编译动态方法生成的汇编程序.这适用于 Windows、macOS 和 Mono 支持的任何 Linux 上的 32/64Bit.
To complete the process, it writes a simple assembler jump into the trampoline of the original method that points to the assembler generated from compiling the dynamic method. This works for 32/64Bit on Windows, macOS and any Linux that Mono supports.
可以在此处找到文档.
(来源)
原始代码
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()
{
// ...
}
}
这篇关于动态替换 C# 方法的内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!