动态IL织入框架Harmony简单入手
Harmony是一个开放源代码库,旨在在运行时替换、修饰或修改任何现有C#方法。它的主要用在用Mono语言编写的游戏和插件,但是该技术可以与任何.NET版本一起使用。它还照顾对同一方法的...
·
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())); //BUG:有Finalizer方法时PassthroughPostfix不生效
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
20
MyPostfix
MyFinalizer
MyTranspiler
MyPrefix True
MyFinalizer
GetNumbers
1, 2, 3
MyTranspiler
StaticPrefix
DoSomething2
3333
MyPostfix
MyFinalizer
Transpiler
Prefix
GetNumbers
Postfix
0, 20, 30, 99
更多推荐
已为社区贡献47条内容
所有评论(0)