【问题标题】:Generated MSIL throws "Common Language Runtime detected an invalid program."生成的 MSIL 抛出“公共语言运行时检测到无效程序”。
【发布时间】:2013-01-28 13:27:12
【问题描述】:

我正在尝试编写动态方法来克隆字典。
下面给出的代码抛出异常:

未处理的异常:System.Reflection.TargetInvocationException:调用的目标已引发异常。 ---> System.InvalidProgramException:公共语言运行时检测到无效程序。 在克隆(字典`2) --- 内部异常堆栈跟踪结束 --- 在 System.RuntimeMethodHandle.InvokeMethod(对象目标,对象 [] 参数,签名 sig,布尔构造函数) 在 System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(对象 obj,对象 [] 参数,对象 [] 参数) 在 System.Delegate.DynamicInvokeImpl(对象 [] 参数) 在 x:\MsilTests\MsilTests\DynamicHelper.cs:line 17 中的 MsilTests.DynamicHelper.Clone[TKey,TValue](Dictionary`2 source) 在 x:\MsilTests\MsilTests\Program.cs:line 11 中的 MsilTests.Program.Main(String[] args)

当我添加行时发生此异常:

generator.Emit(OpCodes.Callvirt, enumeratorType.GetMethod("MoveNext"));

我不知道为什么会抛出这个异常。 如果有人能帮助解决这个问题,我将不胜感激。

    public static class DynamicHelper
    {
        public static Dictionary<TKey, TValue> Clone<TKey, TValue>(Dictionary<TKey, TValue> source)
        {
            var type = typeof (Dictionary<,>).MakeGenericType(typeof (TKey), typeof (TValue));
            var genericFunc = typeof(Func<,>);
            genericFunc = genericFunc.MakeGenericType(type, type);
            var method = new DynamicMethod("Clone", type, new[] { type }, Assembly.GetExecutingAssembly().ManifestModule, true);
            GenerateMsil(method, type);
            return (Dictionary<TKey, TValue>)method.CreateDelegate(genericFunc).DynamicInvoke(source);
        }

        private static void GenerateMsil(DynamicMethod method, Type type)
        {
            var genArgs = type.GetGenericArguments();
            var keyType = genArgs[0];
            var valueType = genArgs[0];
            var pairType = typeof(KeyValuePair<,>).MakeGenericType(keyType, valueType);
            var enumeratorType = typeof(Dictionary<,>.Enumerator).MakeGenericType(keyType, valueType);
            var enumerableType = typeof (IEnumerable<>).MakeGenericType(pairType);

            var generator = method.GetILGenerator();

            var labelRet = generator.DefineLabel();
            var labelEndFinally = generator.DefineLabel();
            var labelMove = generator.DefineLabel();
            var labelWhile = generator.DefineLabel();

            var source = generator.DeclareLocal(type);                  //local_0
            var target = generator.DeclareLocal(type);                  //local_1
            var enumerator = generator.DeclareLocal(enumeratorType);    //local_2
            var pair = generator.DeclareLocal(pairType);                //local_3
            var key = generator.DeclareLocal(keyType);                  //local_4
            var value = generator.DeclareLocal(valueType);              //local_5

/*Stack   */
/*[0]     */
/*[1] +1  */generator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
/*[0] -1  */generator.Emit(OpCodes.Stloc, target);
/*[1] +1  */generator.Emit(OpCodes.Ldarg_0);
/*[0] -1  */generator.Emit(OpCodes.Stloc, source);
/*[1] +1  */generator.Emit(OpCodes.Ldloc, source);
/*[1] -1+1*/generator.Emit(OpCodes.Callvirt, type.GetMethod("GetEnumerator"));
/*[0] -1  */generator.Emit(OpCodes.Stloc, enumerator);

            var tryFinally = generator.BeginExceptionBlock();
            // try {
            generator.Emit(OpCodes.Br_S, labelMove);
            generator.MarkLabel(labelWhile);

/*[1] +1  */generator.Emit(OpCodes.Ldloca, enumerator);
/*[1] -1+1*/generator.Emit(OpCodes.Callvirt, enumeratorType.GetProperty("Current").GetGetMethod());
/*[0] -1  */generator.Emit(OpCodes.Stloc, pair);

/*[1] +1  */generator.Emit(OpCodes.Ldloca, pair);
/*[1] -1+1*/generator.Emit(OpCodes.Callvirt, pairType.GetProperty("Key").GetGetMethod());
/*[0] -1  */generator.Emit(OpCodes.Stloc, key);

/*[1] +1  */generator.Emit(OpCodes.Ldloca, pair);
/*[1] -1+1*/generator.Emit(OpCodes.Callvirt, pairType.GetProperty("Value").GetGetMethod());
/*[0] -1  */generator.Emit(OpCodes.Stloc, value);

/*[2] +1  */generator.Emit(OpCodes.Ldloc, target);
/*[1] +1  */generator.Emit(OpCodes.Ldloc, key);
/*[3] +1  */generator.Emit(OpCodes.Ldloc, value);   
/*[0] -3  */generator.Emit(OpCodes.Callvirt, type.GetMethod("Add"));

            generator.MarkLabel(labelMove);
/*[1] +1  */generator.Emit(OpCodes.Ldloc, enumerator);
/*[1] -1+1*/generator.Emit(OpCodes.Callvirt, enumeratorType.GetMethod("MoveNext"));
/*[0] -1  */generator.Emit(OpCodes.Brtrue_S, labelWhile);
            generator.Emit(OpCodes.Leave_S, labelRet);
            // } finally {
            generator.BeginFinallyBlock();
/*[1] +1  */generator.Emit(OpCodes.Ldloca, enumerator);
/*[0] -1  */generator.Emit(OpCodes.Brfalse_S, labelEndFinally);
/*[1] +1  */generator.Emit(OpCodes.Ldloc, enumerator);
/*[0] -1  */generator.Emit(OpCodes.Callvirt, enumeratorType.GetMethod("Dispose"));
            generator.MarkLabel(labelEndFinally);
            generator.Emit(OpCodes.Endfinally);
            // }
            generator.EndExceptionBlock();
            generator.MarkLabel(labelRet);

/*[1] +1  */generator.Emit(OpCodes.Ldloc, target);
/*[0] -1  */generator.Emit(OpCodes.Ret);
        }
    }

更新: 感谢所有人的帮助和关注我的问题。 我已经解决了这个问题,它包括将 Ldloc 与 Dictionary.Enumerator 一起使用,但是 Dictionary.Enumerator 是一种值类型,使用 Ldloca 将局部变量的地址传递给 Call 或 Callvirt 很重要。 我用正确的代码更新了源代码。

【问题讨论】:

  • 快速尝试一下:您是否尝试过使用 call 而不是 callvirt?另外,“enumeratorType.GetMethod("MoveNext")”的返回值是否正确? (不为空,绑定到正确的方法等?)
  • 我试过 Call, Callvirt - 结果相同。 "enumeratorType.GetMethod("MoveNext")" 返回有效的 MethodInfo。
  • 在这种情况下,将代码生成到程序集然后对其进行 PEVerify 通常非常有用。它会告诉你到底是什么问题。
  • 我使用 PEVerify.exe(/md /il 选项)验证了程序集 - 一切正常。我认为这很有用,因为 IL 在运行时生成。

标签: c# .net cil reflection.emit


【解决方案1】:

GetEnumerator 应该使用CallVirt。在底部你有:

generator.Emit(OpCodes.Ldloc, enumerator); // +1
generator.Emit(OpCodes.Callvirt, enumeratorType.GetMethod("MoveNext")); // -1+1
generator.Emit(OpCodes.Ldloc, target); // +1
generator.Emit(OpCodes.Ret); // should be 0 if void, or 1 for non-void

这是堆栈上的两个值,您还没有弹出。应该只有一个。

如果您不打算使用它,请删除对MoveNext 的调用;这意味着您还需要删除它之前的Ldloc

以下工作(但没有做任何有趣的事情):

// Create instance of Dictionaty<,>
generator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
// Store instance in local variable "target"
generator.Emit(OpCodes.Stloc, target);
// Load first method argument to the stack (for static method - argument, for non-static - instance for with method called)
generator.Emit(OpCodes.Ldarg_0);
// Store argument in local variable "source"
generator.Emit(OpCodes.Stloc, source);
// Load value of local variable "source" to the stack
generator.Emit(OpCodes.Ldloc, source);
// Call method GetEnumerator of type IEnumerable<> 
generator.Emit(OpCodes.Callvirt, type.GetMethod("GetEnumerator"));
// Store value returned from method in local variable "enumerator"
generator.Emit(OpCodes.Stloc, enumerator);

generator.Emit(OpCodes.Ldloc, target);
generator.Emit(OpCodes.Ret);

然而,它在语义上等同于:

generator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
generator.Emit(OpCodes.Ret);

(但不泄露枚举数)

【讨论】:

  • 我将使用 MoveNext。我用 Callvirt 和 Pop 更新代码。发生异常。
  • 我可以添加所有描述“已编译”foreach 实现的代码,使用 try-finally 块并按源循环,但这会使示例更大。
  • @net 没有一个有意义的例子我不能评论 - 因为跟踪确切的堆栈状态是关键;我建议您在每次发出后 编写堆栈应该是什么。例如,在这里搜索CreateParamInfoGeneratorgithub.com/SamSaffron/dapper-dot-net/blob/master/Dapper/…
  • @netdev k;我会尝试稍后再看
猜你喜欢
  • 2018-02-17
  • 1970-01-01
  • 2017-05-05
  • 2011-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-13
  • 1970-01-01
相关资源
最近更新 更多