Сгенерированный MSIL выдает ошибку Common Language Runtime.

Я пытаюсь написать динамический метод, который делает клон словаря ‹,>.
Код, представленный ниже, выдает исключение:

Unhandled Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.InvalidProgramException: Common Language Runtime detected an invalid program.
   at Clone(Dictionary`2 )
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at System.Delegate.DynamicInvokeImpl(Object[] args)
   at MsilTests.DynamicHelper.Clone[TKey,TValue](Dictionary`2 source) in x:\MsilTests\MsilTests\DynamicHelper.cs:line 17
   at MsilTests.Program.Main(String[] args) in x:\MsilTests\MsilTests\Program.cs:line 11

Это исключение возникает, когда я добавил строку:

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. Я обновил исходный код правильным кодом.


person net dev    schedule 28.01.2013    source источник
comment
Просто попробуйте: вы пробовали использовать call вместо callvirt? Кроме того, правильно ли возвращаемое значение enumeratorType.GetMethod (MoveNext)? (Не null, привязать к правильному методу и т. Д.?)   -  person Lorenzo Dematté    schedule 28.01.2013
comment
Пробовал Call, Callvirt - результат тот же. enumeratorType.GetMethod (MoveNext) возвращает действительный MethodInfo.   -  person net dev    schedule 28.01.2013
comment
В таких случаях очень полезно сгенерировать код для сборки, а затем PEVerify это. Он точно скажет вам, в чем проблема.   -  person svick    schedule 28.01.2013
comment
Проверял сборку с помощью PEVerify.exe (параметры / md / il) - все ок. Я думаю, что это полезно, потому что IL генерируется во время выполнения.   -  person net dev    schedule 28.01.2013


Ответы (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);

(но без утечки счетчика)

person Marc Gravell    schedule 28.01.2013
comment
Я собираюсь использовать MoveNext. Я обновляю код с помощью Callvirt и Pop. Возникает исключение. - person net dev; 28.01.2013
comment
Я могу добавить весь код, описывающий скомпилированную реализацию foreach, с блоком try-finally и циклом по источникам, но это увеличивает выборку. - person net dev; 28.01.2013
comment
@net без содержательного примера я не могу комментировать - потому что отслеживание точного состояния стека является ключевым; Я рекомендую вам писать после каждого выброса, каким должен быть стек. Например, введите здесь CreateParamInfoGenerator: github.com/ SamSaffron / dapper-dot-net / blob / master / Dapper / - person Marc Gravell; 28.01.2013
comment
@netdev k; Я постараюсь посмотреть позже - person Marc Gravell; 28.01.2013