通过DynamicMethod调用方法-Reflection.Emit

时间:2018-12-12 11:34:13

标签: c# undefined-behavior reflection.emit emit dynamicmethod

为了动态调用各种类型的TryParse(charintlong),我对this answer进行了稍微修改。

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;
    }
}
  • UB的问题是什么?
  • 为什么使用AccessViolationException?另一个问题或与UB有关?

0 个答案:

没有答案