How to get the parameter value passed as a reference to MethodInfo.Invoke when an exception is thrown in the called method

I would like to know what the out / ref parameter value of the called method is.

When a method is called without exception, the value is accepted in the parameter, but I do not get the value when an exception is thrown in the called method. By calling the method directly without Reflection, the value is accepted.

Am I doing something wrong or is this a .net restriction?

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        string[] arguments = new string[] { bool.FalseString, null }; 
        MethodInfo method = typeof(Program).GetMethod("SampleMethod");
        try
        {
            method.Invoke(null, arguments);
            Console.WriteLine(arguments[1]); // arguments[1] = "Hello", Prints Hello
            arguments = new string[] { bool.TrueString, null };
            method.Invoke(null, arguments);
        }
        catch (Exception)
        {
            Console.WriteLine(arguments[1]); // arguments[1] = null, Does not print
        }
        arguments[1] = null;
        try
        {
            SampleMethod(bool.TrueString, out arguments[1]);
        }
        catch (Exception)
        {
            Console.WriteLine(arguments[1]); // arguments[1] = "Hello"
        }
    }

    public static void SampleMethod(string throwsException, out string text)
    {
        text = "Hello";
        if (throwsException == bool.TrueString)
            throw new Exception("Test Exception");
    }
}

After searching for a small bit, I found a solution below. Would that be nice to use?

using System;
using System.Reflection;
using System.Reflection.Emit;

public static class MethodInfoExtension
{
    public static object InvokeStrictly(this MethodInfo source, object obj, object[] parameters)
    {
        ParameterInfo[] paramInfos = source.GetParameters();
        if ((parameters == null) || (paramInfos.Length != parameters.Length))
        {
            throw new ArgumentException();
        }

        Type[] paramTypes = new[] { typeof(object[]) };
        DynamicMethod invokerBuilder = new DynamicMethod(string.Empty, typeof(object), paramTypes);

        ILGenerator ilGenerator = invokerBuilder.GetILGenerator();
        Label exBlockLabel = ilGenerator.BeginExceptionBlock();

        for (int i = 0; i < paramInfos.Length; i++)
        {
            var paramInfo = paramInfos[i];
            bool paramIsByRef = paramInfo.ParameterType.IsByRef;
            var paramType = paramIsByRef ? paramInfo.ParameterType.GetElementType() : paramInfo.ParameterType;

            ilGenerator.DeclareLocal(paramType);

            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldelem_Ref);
            Label label1 = ilGenerator.DefineLabel();
            ilGenerator.Emit(OpCodes.Brfalse, label1);

            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldelem_Ref);
            ilGenerator.Emit(OpCodes.Unbox_Any, paramType);
            ilGenerator.Emit(OpCodes.Stloc_S, (byte)i);

            ilGenerator.MarkLabel(label1);

            if (paramIsByRef)
            {
                ilGenerator.Emit(OpCodes.Ldloca_S, (byte)i);
            }
            else
            {
                ilGenerator.Emit(OpCodes.Ldloc_S, (byte)i);
            }
        }

        LocalBuilder resultLocal = ilGenerator.DeclareLocal(typeof(object), false);
        ilGenerator.Emit(OpCodes.Call, source);
        if (source.ReturnType == typeof(void))
        {
            ilGenerator.Emit(OpCodes.Ldnull);
        }
        ilGenerator.Emit(OpCodes.Stloc_S, resultLocal);
        ilGenerator.Emit(OpCodes.Leave, exBlockLabel);

        ilGenerator.BeginFinallyBlock();
        for (int i = 0; i < paramInfos.Length; i++)
        {
            var paramInfo = paramInfos[i];
            bool paramIsByRef = paramInfo.ParameterType.IsByRef;
            var paramType = paramIsByRef ? paramInfo.ParameterType.GetElementType() : paramInfo.ParameterType;

            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldloc_S, (byte)i);
            if (paramType.IsValueType)
            {
                ilGenerator.Emit(OpCodes.Box, paramType);
            }
            ilGenerator.Emit(OpCodes.Stelem, typeof(object));
        }
        ilGenerator.EndExceptionBlock();

        ilGenerator.Emit(OpCodes.Ldloc_S, resultLocal);
        ilGenerator.Emit(OpCodes.Ret);

        var invoker = (Func<object[], object>)invokerBuilder.CreateDelegate(typeof(Func<object[], object>));
        return invoker(parameters);
    }
}

public class Program
{
    static void Main()
    {
        object[] args = new object[1];
        try
        {
            MethodInfo targetMethod = typeof(Program).GetMethod("Method");
            targetMethod.InvokeStrictly(null, args);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            Console.WriteLine();
        }
        Console.WriteLine(args[0]);
        Console.ReadLine();
    }
    public static void Method(out string arg)
    {
        arg = "Hello";
        throw new Exception("Test Exception");
    }
}
+6
source share
1 answer

In short, you are not doing anything wrong. Its restriction on implementation when called.

ref . , .


...

, , IL. :

.method public hidebysig static void SampleMethod(string throwsException, [out] string& text) cil managed
 {
    // 
    .maxstack  2
    .locals init (bool V_0)
    IL_0000:  nop
    IL_0001:  ldarg.1             // Get argument 2
    IL_0002:  ldstr      "Hello"  // Get string literal
    IL_0007:  stind.ref           // store in reference address
    IL_0008:  ldarg.0
    IL_0009:  ldsfld     string [mscorlib]System.Boolean::TrueString
    IL_000e:  call       bool   [mscorlib]System.String::op_Equality(string, string)
    IL_0013:  ldc.i4.0
    IL_0014:  ceq
    IL_0016:  stloc.0
    IL_0017:  ldloc.0
    IL_0018:  brtrue.s   IL_0025

    IL_001a:  ldstr      "Test Exception"
    IL_001f:  newobj     instance void [mscorlib]System.Exception::.ctor(string)
    IL_0024:  throw

    IL_0025:  ret
} // end of method Program::SampleMethod

, "Hello" () . , .

invoke . IL , , , . Invoke:

public override Object Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
{
    object[] arguments = InvokeArgumentsCheck(obj, invokeAttr, binder, parameters, culture);    
    // [Security Check omitted for readability]   
    return UnsafeInvokeInternal(obj, parameters, arguments);
}

, InvokeArgumentsCheck, , . :

internal Object[] CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
{
    // copy the arguments in a different array so we detach from any user changes 
    Object[] copyOfParameters = new Object[parameters.Length];
    // [Code omitted for readability]
    for (int i = 0; i < parameters.Length; i++)
    {
        // [Code omitted for readability]
        copyOfParameters[i] = argRT.CheckValue(arg, binder, culture, invokeAttr);
    }

    return copyOfParameters;
}

, ( ). , , , , .

UnsafeInvokeInternal. :

private object UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
{
    if (arguments == null || arguments.Length == 0)
        return RuntimeMethodHandle.InvokeMethod(obj, null, Signature, false);
    else
    {
        Object retValue = RuntimeMethodHandle.InvokeMethod(obj, arguments, Signature, false);

        // copy out. This should be made only if ByRef are present.
        for (int index = 0; index < arguments.Length; index++)
            parameters[index] = arguments[index];

        return retValue;
    }
}

"else". , , , . "".

, , "" "Hello" . ( ), arguments, .

, , , .

0

All Articles