You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Harmony Transpiler字段引用空值异常问题排查

Harmony Transpiler字段替换异常问题分析与解决

问题场景

尝试用Harmony的Transpiler修改Boosterpack.Clicked方法,把硬编码的常量5和10替换为OneVillagerChecks类的静态字段,代码如下:

namespace DifficultyModNS
{
    [HarmonyPatch(typeof(Boosterpack),nameof(Boosterpack.Clicked))]
    public class OneVillagerChecks
    {
        public static Int32 frequency = 5;
        public static Int32 startChecking = 10;

        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            foreach (var instruction in instructions)
            {
                if (instruction.opcode == OpCodes.Ldc_I4_5)
                {
                    DifficultyMod.Log("Transpiling const 5 to variable");
                    yield return new CodeInstruction(OpCodes.Ldfld, operand: AccessTools.Field(typeof(Int32), name: "DifficultyModNS.OneVillagerChecks::frequency"));
                }
                else if (instruction.opcode == OpCodes.Ldc_I4_S && Convert.ToInt32(instruction.operand) == 10)
                {
                    DifficultyMod.Log("Transpiling const 10 to variable");
                    yield return new CodeInstruction(OpCodes.Ldfld, operand: AccessTools.Field(typeof(Int32), name: "DifficultyModNS.OneVillagerChecks::startChecking"));
                }
                else
                {
                    yield return instruction;
                }
            }
        }
    }
}

错误现象

第一条日志正常打印,但第二条日志输出后立即抛出异常:

[17:18:44] [Log : difficulty_mod] Transpiling const 5 to variable
[17:18:44] [Log : difficulty_mod] Transpiling const 10 to variable
ArgumentNullException: Invalid argument for ldfld NULL
Parameter name: operand
  at HarmonyLib.Internal.Patching.ILManipulator.WriteTo (Mono.Cecil.Cil.MethodBody body, System.Reflection.MethodBase original) [0x001b4] in <4a2409e6eb214388b8d6ab81d407350d>:0 
  at HarmonyLib.Public.Patching.HarmonyManipulator.WriteTranspilers () [0x00084] in <4a2409e6eb214388b8d6ab81d407350d>:0 
  at HarmonyLib.Public.Patching.HarmonyManipulator.WriteImpl () [0x00031] in <4a2409e6eb214388b8d6ab81d407350d>:0 
Rethrow as HarmonyException: IL Compile Error (unknown location)
  at HarmonyLib.Public.Patching.HarmonyManipulator.WriteImpl () [0x00382] in <4a2409e6eb214388b8d6ab81d407350d>:0 
  at HarmonyLib.Public.Patching.HarmonyManipulator.Process (MonoMod.Cil.ILContext ilContext, System.Reflection.MethodBase originalMethod) [0x00042] in <4a2409e6eb214388b8d6ab81d407350d>:0 
  at HarmonyLib.Public.Patching.HarmonyManipulator.Manipulate (System.Reflection.MethodBase original, HarmonyLib.PatchInfo patchInfo, MonoMod.Cil.ILContext ctx) [0x00006] in <4a2409e6eb214388b8d6ab81d407350d>:0 
  at HarmonyLib.Public.Patching.HarmonyManipulator.Manipulate (System.Reflection.MethodBase original, MonoMod.Cil.ILContext ctx) [0x00007] in <4a2409e6eb214388b8d6ab81d407350d>:0 
  at HarmonyLib.Public.Patching.ManagedMethodPatcher.Manipulator (MonoMod.Cil.ILContext ctx) [0x00012] in <4a2409e6eb214388b8d6ab81d407350d>:0 
  at MonoMod.Cil.ILContext.Invoke (MonoMod.Cil.ILContext+Manipulator manip) [0x00087] in <92bbbc5a09fc417ca7426fbdd31824c2>:0 
  at MonoMod.RuntimeDetour.ILHook+Context.InvokeManipulator (Mono.Cecil.MethodDefinition def, MonoMod.Cil.ILContext+Manipulator cb) [0x00012] in <925d74c1e4e3467a888e7df55b8370c5>:0 
  at (wrapper dynamic-method) MonoMod.RuntimeDetour.ILHook+Context.DMD<MonoMod.RuntimeDetour.ILHook+Context::Refresh>(MonoMod.RuntimeDetour.ILHook/Context)
  at (wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.Trampoline<MonoMod.RuntimeDetour.ILHook+Context::Refresh>?1194592624(object)
  at HarmonyLib.Internal.RuntimeFixes.StackTraceFixes.OnILChainRefresh (System.Object self) [0x00000] in <4a2409e6eb214388b8d6ab81d407350d>:0 
  at MonoMod.RuntimeDetour.ILHook.Apply () [0x00059] in <925d74c1e4e3467a888e7df55b8370c5>:0 
  at HarmonyLib.Public.Patching.ManagedMethodPatcher.DetourTo (System.Reflection.MethodBase replacement) [0x00047] in <4a2409e6eb214388b8d6ab81d407350d>:0 
// 后续为子系统重复抛出的异常

最初疑问:同一类中相邻的两个字段,为什么一个替换成功另一个失败?

后续排查:

  • 编辑1:移除失效分支的else if后,异常仍在函数退出后触发,疑问转为:字段名称存在但Ldfld的操作数返回NULL?
  • 编辑2:最终找到问题并解决。

问题根源

  1. IL指令使用错误:要访问的是静态字段,应该用OpCodes.Ldsfld指令(加载静态字段到栈),但代码中误用了OpCodes.Ldfld(加载实例字段,需要栈上先有类的实例引用),这会直接导致IL验证失败。
  2. AccessTools.Field调用错误:调用AccessTools.Field(typeof(Int32), "DifficultyModNS.OneVillagerChecks::frequency")时,第一个参数传了字段的类型Int32,但正确的应该传字段所属的类OneVillagerChecks的Type,导致AccessTools无法找到目标字段,返回NULL,进而触发ArgumentNullException。

修正后的代码

namespace DifficultyModNS
{
    [HarmonyPatch(typeof(Boosterpack),nameof(Boosterpack.Clicked))]
    public class OneVillagerChecks
    {
        public static Int32 frequency = 5;
        public static Int32 startChecking = 10;

        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            // 直接获取字段信息,避免AccessTools的使用误区
            var frequencyField = typeof(OneVillagerChecks).GetField(nameof(frequency));
            var startCheckingField = typeof(OneVillagerChecks).GetField(nameof(startChecking));
            
            foreach (var instruction in instructions)
            {
                if (instruction.opcode == OpCodes.Ldc_I4_5)
                {
                    DifficultyMod.Log("Transpiling const 5 to variable");
                    yield return new CodeInstruction(OpCodes.Ldsfld, frequencyField);
                }
                else if (instruction.opcode == OpCodes.Ldc_I4_S && Convert.ToInt32(instruction.operand) == 10)
                {
                    DifficultyMod.Log("Transpiling const 10 to variable");
                    yield return new CodeInstruction(OpCodes.Ldsfld, startCheckingField);
                }
                else
                {
                    yield return instruction;
                }
            }
        }
    }
}

总结

  • 静态字段访问必须用Ldsfld,实例字段才用Ldfld,二者不可混淆;
  • 获取字段信息时,要明确字段所属的类类型,而非字段本身的类型,直接用Type.GetField更直观不易出错。

内容的提问来源于stack exchange,提问作者jmucchiello

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 17:24:55