一. Harmony工作原理
利用C#运行时Runtime的反射机制,动态加载dll中的方法,字段,属性,实现对DLL方法的重写和代码注入。
二. Harmony下载及csproj文件引用Harmony库
1.下载Harmony_lib库lib.harmony.2.3.3.nupkg
Harmony API (pardeike.net)https://harmony.pardeike.net/api/index.htmlNuGet Gallery | Lib.Harmony 2.3.3
https://www.nuget.org/packages/Lib.Harmony
2.csproj添加reference引用
<Reference Include="0Harmony">
<HintPath>..\..\Tool\C#\lib.harmony.2.3.3\lib\net48\0Harmony.dll</HintPath>
</Reference>
三. 激活所有HarmonyPatch
扫描所有类,如果含有HarmonyPatch注解,则该类实现的Patch将会被激活
var harmonyPatch = new Harmony("patch");
harmonyPatch.PatchAll();
四. 实现目标方法HarmonyPatch
<1.构造方法
[HarmonyPatch(typeof(OriginalClass), MethodType.Constructor)]
public class MyHarmonyPatch
{
}
<2.字段内置Setter/Getter方法
[HarmonyPatch(typeof(OriginalClass), "IsLoaded", MethodType.Getter)]
public class MyHarmonyPatch
{
}
<3.静态方法&实例方法
[HarmonyPatch(typeof(OriginalClass), "OriginalMethod")]
public class MyHarmonyPatch
{
}
<4.重载方法
[HarmonyPatch(typeof(OriginalClass), "OriginalMethod", new Type[]{})]
public class MyHarmonyPatch
{
}
[HarmonyPatch(typeof(OriginalClass), "OriginalMethod", new Type[1]{typeof(string)})]
public class MyHarmonyPatch
{
}
五. HarmonyPatch拦截
通过反射创建目标类的代理,可实现对方法调用前,方法调用后,方法异常等进行拦截
<1.Harmony拦截原理
通过C#运行时CIR环境中,通过动态反射创建目标类的代理类delegate,在原方法调用时调用delegate指定的方法.
Harmony实现了前置拦截,后置拦截和异常拦截
<2.Harmony约定代理参数格式
目标类实例instance:
入参类型: 类类型
入参变量名: __instance(固定为__instance)
目标类实例私有字段field:
入参类型: 字段类型
入参变量名: ___param(___ + 原私有字段变量名)
目标方法入参param:
入参类型: 参数类型
入参变量名: ref arg(ref标识 + 原方法变量名)
目标方法返回值result:
入参类型: object
入参变量名: ref __result(固定为__result)
<3.Prefix前置(在方法调用前调用)
[HarmonyPatch(typeof(OriginalClass), "IsLoaded")]
public class MyHarmonyPatch
{
public static bool Prefix(object __instance, object __result)
{
//__instance调用者实例,可使用后续AccessTool获取该实例相关信息
__instance.pro
//修改原方法的返回值
__result = 2
//返回True 原方法继续运行 False 原方法不执行
return true;
}
}
<4.Postfix:后置插桩(在方法调用后调用)
[HarmonyPatch(typeof(OriginalClass), "IsLoaded")]
public class MyHarmonyPatch
{
public static void Postfix(object __instance, object __result)
{
//__instance调用者实例,可使用后续AccessTool获取该实例相关信息
__instance.pro
//修改原方法的返回值
__result = 2
}
}
<5.Finalizer:异常捕获(在方法发生异常时调用)
[HarmonyPatch(typeof(OriginalClass), "IsLoaded")]
public class MyHarmonyPatch
{
public static void Finalizer(Exception __exception)
{
if (__exception == null)
{
return;
}
}
}
六. Assembly
使用Assembly访问私有/受保护的类
Type internalClassType = Assembly.Load("test").GetType("test.test");
七. Traverse
使用Traverse访问类的私有/受保护的字段&方法
<1.获取私有/受保护的字段
FieldInfo fieldInfo = Traverse.Create(originalClass).Field("a");
<2.获取私有/受保护的方法
八. AccessTool
使用AccessTool访问类的私有/受保护的字段&方法
<1.获取私有/受保护的字段
FieldInfo info = AccessTools.Field(typeof(OriginalClass), "b");
<2.获取私有/受保护的方法
1.构造方法
ConstructorInfo method = AccessTools.Constructor(typeof(OriginalClass), new Type[0]{});
2.字段内置Setter/Getter方法
MethodInfo isLoadedSetter = AccessTools.PropertySetter(typeof(OriginalClass), "IsLoaded");
3.静态方法&实例方法
MethodInfo method = AccessTools.Method(typeof(OriginalClass), "OtherMethod");
九. MethodInfo
获取私有/受保护方法后会得到MethodInfo实例
<1.对方法进行调用
1.调用静态方法
method.Invoke(null, new object[0]);
方法中含有ref, in, out等引用传递时,通过将参数值设为null,调用后取出
原方法: void AddMusic(int a, int b, out string c)
object[] parameters = new object[]{1, 2, null}
method.Invoke(null, parameters);
string c = parameters[2]
2.调用实例方法&字段内置Setter/Getter方法
method.Invoke(__instance, new object[0]);
方法中含有ref, in, out等引用传递时,通过将参数值设为null,调用后取出
原方法: void AddMusic(int a, int b, out string c)
object[] parameters = new object[]{1, 2, null}
method.Invoke(null, parameters);
string c = parameters[2]
3.调用构造方法
method.Invoke(new object[0]);
方法中含有ref, in, out等引用传递时,通过将参数值设为null,调用后取出
原方法: void AddMusic(int a, int b, out string c)
object[] parameters = new object[]{1, 2, null}
method.Invoke(null, parameters);
string c = parameters[2]
<2.方法转化为Action/Func代理
1.获取Action代理
var delegate = methodInfo.CreateDelegate(typeof(Action), __instance);
Action<Test> action = (Action<Test>)(object)delegate
2.获取Func代理
var delegate = methodInfo.CreateDelegate(typeof(Func<PlayMusic>), __instance);
Func<PlayMusic> func = (Func<PlayMusic>)(object)delegate;
十. FieldInfo
获取私有/受保护字段后会得到FieldInfo实例
<1.获取字段值
int value = fieldInfo.GetValue<int>()
<2.设置字段值
int value = fieldInfo.SetValue<int>()