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

向Setter插入方法调用引发Invalid IL代码问题排查与扩展需求

问题描述

我给部分属性标记了自定义NetworkVariable特性,希望在这些属性的Setter完成赋值后,调用其所属类父类NetworkBehaviour中的LocalNetworkVariableChange方法(该方法接收一个int类型参数,即属性的数组索引)。

我尝试通过ILProcessor修改Setter的IL代码,但运行时触发InvalidProgramException异常。以下是相关代码及报错信息:

原Setter的IL代码

IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stfld class [Assembly-CSharp]MyType MyClass::myField
IL_0007: ret

修改后的IL代码

IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stfld class [Assembly-CSharp]MyType MyClass::myField
IL_0007: ldarg.0
IL_0008: ldc.i4 0
IL_000d: call instance void [Mirror]Mirror.NetworkBehaviour::LocalNetworkVariableChange(int32)
IL_0012: ret

报错信息

InvalidProgramException: Common Language Runtime detected an invalid program.

用于修改的ProcessAssembly代码

public static void ProcessAssembly(Assembly assembly)
{
    foreach (var type in assembly.GetTypes())
    {
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            var attr = prop.GetCustomAttribute<NetworkVariableAttribute>();
            if (attr == null) continue;

            var setter = prop.SetMethod;
            if (setter == null) continue;

            var ilProcessor = setter.GetILProcessor();
            var retInstruction = ilProcessor.Body.Instructions.Last();
            
            ilProcessor.InsertBefore(retInstruction, ilProcessor.Create(OpCodes.Ldarg_0));
            ilProcessor.InsertBefore(retInstruction, ilProcessor.Create(OpCodes.Ldc_I4, attr.Index));
            var method = typeof(NetworkBehaviour).GetMethod("LocalNetworkVariableChange", BindingFlags.Instance | BindingFlags.NonPublic);
            ilProcessor.InsertBefore(retInstruction, ilProcessor.Create(OpCodes.Call, method));
        }
    }
}

父类LocalNetworkVariableChange方法实现

protected void LocalNetworkVariableChange(int index)
{
    Debug.Log($"Variable at index {index} changed");
}

疑问

  1. 我的IL修改操作哪里有误?
  2. 后续希望扩展方法参数,传入属性的新值,该如何实现?

问题分析与解决

1. InvalidProgramException的原因及修正

常见错误点

  • 仅修改了最后一条ret指令:如果Setter内部存在分支逻辑(比如参数校验、条件赋值),会有多条ret指令跳出方法,只修改最后一条会导致部分执行路径未插入调用代码,甚至破坏栈平衡。
  • 方法引用或调用指令错误:若LocalNetworkVariableChange是父类非公开方法,需确保获取MethodInfo时绑定标志正确(BindingFlags.Instance | BindingFlags.NonPublic),调用时指令与方法匹配。
  • 栈状态不平衡:插入的IL指令需保证栈元素数量与方法参数要求一致,若指令顺序错误或遗漏,会导致CLR检测到无效程序。

修正后的ProcessAssembly代码

public static void ProcessAssembly(Assembly assembly)
{
    var targetMethod = typeof(NetworkBehaviour).GetMethod(
        "LocalNetworkVariableChange",
        BindingFlags.Instance | BindingFlags.NonPublic);
    if (targetMethod == null)
        throw new InvalidOperationException("LocalNetworkVariableChange method not found");

    foreach (var type in assembly.GetTypes())
    {
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            var attr = prop.GetCustomAttribute<NetworkVariableAttribute>();
            if (attr == null) continue;

            var setter = prop.SetMethod;
            if (setter == null) continue;

            var ilProcessor = setter.GetILProcessor();
            var instructions = setter.Body.Instructions.ToList();
            
            // 遍历所有ret指令,确保所有执行路径都插入调用
            foreach (var retInst in instructions.Where(inst => inst.OpCode == OpCodes.Ret).ToList())
            {
                // 按顺序插入:加载当前实例 -> 加载属性索引 -> 调用目标方法
                ilProcessor.InsertBefore(retInst, ilProcessor.Create(OpCodes.Ldarg_0));
                ilProcessor.InsertBefore(retInst, ilProcessor.Create(OpCodes.Ldc_I4, attr.Index));
                ilProcessor.InsertBefore(retInst, ilProcessor.Create(OpCodes.Call, targetMethod));
            }
        }
    }
}

2. 扩展参数传入新值的实现

步骤1:修改父类方法

更新LocalNetworkVariableChange方法,添加新值参数(用object兼容所有类型,或根据需求使用泛型):

protected void LocalNetworkVariableChange(int index, object newValue)
{
    Debug.Log($"Variable at index {index} changed to {newValue}");
}

步骤2:修改IL插入逻辑

在调用方法前,加载Setter的第二个参数(即属性的新值,对应IL指令Ldarg_1),调整方法调用的参数匹配:

public static void ProcessAssembly(Assembly assembly)
{
    var targetMethod = typeof(NetworkBehaviour).GetMethod(
        "LocalNetworkVariableChange",
        BindingFlags.Instance | BindingFlags.NonPublic,
        new[] { typeof(int), typeof(object) });
    if (targetMethod == null)
        throw new InvalidOperationException("Updated LocalNetworkVariableChange method not found");

    foreach (var type in assembly.GetTypes())
    {
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            var attr = prop.GetCustomAttribute<NetworkVariableAttribute>();
            if (attr == null) continue;

            var setter = prop.SetMethod;
            if (setter == null) continue;

            var ilProcessor = setter.GetILProcessor();
            var instructions = setter.Body.Instructions.ToList();
            
            foreach (var retInst in instructions.Where(inst => inst.OpCode == OpCodes.Ret).ToList())
            {
                // 按顺序插入:加载实例 -> 加载索引 -> 加载新值 -> 调用方法
                ilProcessor.InsertBefore(retInst, ilProcessor.Create(OpCodes.Ldarg_0));
                ilProcessor.InsertBefore(retInst, ilProcessor.Create(OpCodes.Ldc_I4, attr.Index));
                ilProcessor.InsertBefore(retInst, ilProcessor.Create(OpCodes.Ldarg_1));
                ilProcessor.InsertBefore(retInst, ilProcessor.Create(OpCodes.Call, targetMethod));
            }
        }
    }
}

值类型会自动装箱为object,无需额外处理;若需强类型支持,可针对不同属性类型生成对应调用逻辑,但复杂度会更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 03:13:26