向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"); }
疑问
- 我的IL修改操作哪里有误?
- 后续希望扩展方法参数,传入属性的新值,该如何实现?
问题分析与解决
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
相关产品推荐
相关产品推荐

