Reflection.Emit数组参数入栈调用函数时CLR无效程序异常排查
问题解答
一、为什么循环入栈会触发栈高度不足/无效程序异常
- 参数传递逻辑错误:目标方法
Dictionary.ByKeysValues(IList<string>,IList<object>)需要的是两个IList实例作为参数,而非IList内部的元素。如果你循环将Utils.UnMarshalFunctionArgs2转换后的数组中的子元素(比如键列表里的每个string、值列表里的每个object)入栈,栈上的参数数量、类型完全不符合方法签名,CLR验证时会判定栈状态非法,抛出异常。 - IL栈操作不严谨:循环过程中可能存在栈管理失误,比如:
- 循环前未正确保留数组引用,导致
ldelem操作时栈上没有数组实例,触发栈下溢; - 循环次数与数组实际元素数量不匹配(比如循环次数多了/少了),导致栈上参数数量不对;
- 入栈顺序与方法参数顺序相反,CLR无法匹配参数类型。
- 循环前未正确保留数组引用,导致
- CLR的IL验证机制:CLR会严格检查IL代码的栈高度一致性,如果你生成的IL在调用方法前,栈上的元素数量、类型与方法签名要求的参数列表不匹配,就会直接判定为无效程序。
二、用IL正确调用目标方法的步骤
假设Utils.UnMarshalFunctionArgs2的返回值是object[],其中索引0对应IList<string>、索引1对应IList<object>(即目标方法的两个参数),正确的IL生成逻辑如下:
1. 核心IL操作流程
- 调用
Utils.UnMarshalFunctionArgs2获取参数数组并压入栈; - 从数组中取出第一个参数(
IList<string>):压入数组引用→压入索引0→调用ldelem.ref取出元素压栈; - 从数组中取出第二个参数(
IList<object>):再次压入数组引用→压入索引1→调用ldelem.ref取出元素压栈; - 调用
Dictionary.ByKeysValues方法。
2. 代码示例(C# Reflection.Emit)
// 假设已获取到对应方法的MethodInfo var unMarshalMethod = typeof(Utils).GetMethod("UnMarshalFunctionArgs2", /* 匹配的参数类型 */); var targetMethod = typeof(Dictionary).GetMethod("ByKeysValues", new[] { typeof(IList<string>), typeof(IList<object>) }); // 创建DynamicMethod并生成IL var dm = new DynamicMethod("CallByKeysValues", typeof(void), Type.EmptyTypes); var il = dm.GetILGenerator(); // 1. 调用UnMarshalFunctionArgs2获取参数数组 il.Emit(OpCodes.Call, unMarshalMethod); // 栈顶:object[] argsArray // 2. 取出第一个参数IList<string>(索引0) il.Emit(OpCodes.Dup); // 复制数组引用,栈:argsArray, argsArray il.Emit(OpCodes.Ldc_I4_0); // 压入索引0,栈:argsArray, argsArray, 0 il.Emit(OpCodes.Ldelem_Ref); // 取出argsArray[0],栈:argsArray, IList<string> // 3. 取出第二个参数IList<object>(索引1) il.Emit(OpCodes.Dup); // 复制数组引用,栈:argsArray, IList<string>, argsArray il.Emit(OpCodes.Ldc_I4_1); // 压入索引1,栈:argsArray, IList<string>, argsArray, 1 il.Emit(OpCodes.Ldelem_Ref); // 取出argsArray[1],栈:argsArray, IList<string>, IList<object> // 4. 弹出多余的数组引用(避免栈泄漏) il.Emit(OpCodes.Pop); // 栈:IList<string>, IList<object> // 5. 调用目标方法 il.Emit(OpCodes.Call, targetMethod); // 6. 返回 il.Emit(OpCodes.Ret);
关键注意点
- 参数顺序严格匹配:目标方法的第一个参数是
IList<string>,第二个是IList<object>,入栈顺序必须和方法签名一致; - 栈操作精准:每次
ldelem前必须确保栈上有数组引用和正确的索引值,避免栈空或类型不匹配; - 清理冗余元素:不需要的数组引用要及时
Pop,保证调用方法前栈上只有正确数量的参数。
内容的提问来源于stack exchange,提问作者mjk
相关产品推荐
相关产品推荐

