Вызов метода через DynamicMethod - Reflection.Emit - PullRequest
0 голосов
/ 12 декабря 2018

У меня есть слегка измененный класс этого ответа , чтобы динамически вызывать TryParse различных типов (char, int, long).

public delegate TRet DynamicMethodDelegate<TRet>(object target, params object[] args);
public delegate void DynamicMethodDelegate(object target, params object[] args);

public class DynamicMethodDelegateFactory
{
    public static TDelegate CreateMethodCaller<TDelegate>(MethodInfo method)
        where TDelegate : class
    {
        ParameterInfo[] parameters = method.GetParameters();
        Type[] args = { typeof(object), typeof(object[]) };

        DynamicMethod dynam =
            new DynamicMethod
                (
                    method.Name
                    , method.ReturnType
                    , args
                    , typeof(DynamicMethodDelegateFactory)
                    , true
                );

        //Add parmeter attributes to the new method from the existing method
        for (int i = 0; i < parameters.Length; i++)
        {
            dynam.DefineParameter
            (
                i,
                parameters[i].Attributes,
                parameters[i].Name
            );
        }

        ILGenerator il = dynam.GetILGenerator();

        // If method isn't static push target instance on top of stack.
        if (!method.IsStatic)
        {
            // Argument 0 of dynamic method is target instance.
            il.Emit(OpCodes.Ldarg_0);
        }

        // Lay out args array onto stack.    
        LocalBuilder[] locals = new LocalBuilder[parameters.Length];
        List<LocalBuilder> outOrRefLocals = new List<LocalBuilder>();
        for (int i = 0; i < parameters.Length; i++)
        {
            //Push args array reference onto the stack, followed
            //by the current argument index (i). The Ldelem_Ref opcode
            //will resolve them to args[i].
            if (!parameters[i].IsOut)
            {
                // Argument 1 of dynamic method is argument array.
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Ldc_I4, i);
                il.Emit(OpCodes.Ldelem_Ref);
            }

            // If parameter [i] is a value type perform an unboxing.
            Type parameterType = parameters[i].ParameterType;
            if (parameterType.IsValueType)
            {
                il.Emit(OpCodes.Unbox_Any, parameterType);
            }
        }

        //Create locals for out parameters
        for (int i = 0; i < parameters.Length; i++)
        {
            if (parameters[i].IsOut)
            {
                locals[i] = il.DeclareLocal(parameters[i].ParameterType.GetElementType());
                il.Emit(OpCodes.Ldloca, locals[locals.Length - 1]);
            }
        }

        if (method.IsFinal || !method.IsVirtual)
        {
            il.Emit(OpCodes.Call, method);
        }
        else
        {
            il.Emit(OpCodes.Callvirt, method);
        }

        for (int idx = 0; idx < parameters.Length; ++idx)
        {
            if (parameters[idx].IsOut || parameters[idx].ParameterType.IsByRef)
            {
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Ldc_I4, idx);
                il.Emit(OpCodes.Ldloc, locals[idx].LocalIndex);

                if (parameters[idx].ParameterType.GetElementType().IsValueType)
                    il.Emit(OpCodes.Box, parameters[idx].ParameterType.GetElementType());

                il.Emit(OpCodes.Stelem_Ref);
            }
        }

        if (method.ReturnType != typeof(void))
        {
            // If result is of value type it needs to be boxed
            if (method.ReturnType.IsValueType)
            {
                il.Emit(OpCodes.Box, method.ReturnType);
            }
        }
        else
        {
            il.Emit(OpCodes.Ldnull);
        }

        il.Emit(OpCodes.Ret);

        return dynam.CreateDelegate(typeof(TDelegate)) as TDelegate;
    }
}

К сожалению, это выдает AccessViolationException, и после проверки кода немного более подробно, я все еще не уверен, почему.

Этот код также "работает" в другом проекте, где кажется, что возвращаемое значение непоследовательны.Иногда он просто возвращает false, когда фактический анализ с помощью TryParse успешен.Это звучит как неопределенное поведение, но я не могу найти проблему (ы).

Вот пример кода AccessViolationException И неопределенного поведения (удалите значение с плавающей точкой из массива для UB).

static void Main(string[] args)
{
    var arr = new object[] { 'A', (byte)1, (short)2, 3, 4L, 5M, 6.0, 7.0F, "8" };

    for (int i = 0; i < 100000; i++)
    {
        foreach (var item in arr)
            ParseTest(item);
    }

    int a = 1;
    int b = a;
}

static Type StringType = typeof(string);
static bool ParseTest(object data)
{
    var type = data.GetType();
    if (type == StringType)
        return true;
    else
    {
        var mi = type.GetMethod(nameof(int.TryParse), new[] { StringType, type.MakeByRefType() });
        var dmd = DynamicMethodDelegateFactory.CreateMethodCaller<DynamicMethodDelegate<bool> >(mi);

        dynamic dummy = null;
        var args = new object[] { data, dummy };

        var ok = dmd(null, args);

        return ok;
    }
}
  • В чем проблема УБ?
  • Почему AccessViolationException?Другая проблема или связано с UB?
...