File: System\ServiceModel\Dispatcher\InvokerUtil.cs
Web Access
Project: src\src\System.ServiceModel.Primitives\src\System.ServiceModel.Primitives.csproj (System.ServiceModel.Primitives)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.ServiceModel.Description;
using System.Text;
 
namespace System.ServiceModel.Dispatcher
{
    internal delegate object InvokeDelegate(object target, object[] inputs, object[] outputs);
 
    internal sealed class InvokerUtil
    {
        private static readonly string s_useLegacyInvokeDelegateAppContextSwitchKey = "System.ServiceModel.Dispatcher.UseLegacyInvokeDelegate";
 
        private static readonly Lazy<bool> s_useLegacyInvokeDelegate = new Lazy<bool>(() =>
            AppContext.TryGetSwitch(s_useLegacyInvokeDelegateAppContextSwitchKey, out bool useLegacyInvokeDelegate)
                ? useLegacyInvokeDelegate
                : false
        );
 
        private readonly CriticalHelper _helper;
 
        public InvokerUtil()
        {
            _helper = new CriticalHelper();
        }
 
        internal InvokeDelegate GenerateInvokeDelegate(MethodInfo method, out int inputParameterCount,
            out int outputParameterCount)
        {
            if (!s_useLegacyInvokeDelegate.Value && RuntimeFeature.IsDynamicCodeSupported)
            {
                return _helper.GenerateInvokeDelegateInternalWithExpressions(method, out inputParameterCount, out outputParameterCount);
            }
 
            return _helper.GenerateInvokeDelegate(method, out inputParameterCount, out outputParameterCount);
        }
 
        private class CriticalHelper
        {
            internal InvokeDelegate GenerateInvokeDelegate(MethodInfo method, out int inputParameterCount, out int outputParameterCount)
            {
                ParameterInfo[] parameters = method.GetParameters();
                bool returnsValue = method.ReturnType != typeof(void);
                var inputCount = parameters.Length;
                inputParameterCount = inputCount;
 
                var outputParamPositions = new List<int>();
                for (int i = 0; i < inputParameterCount; i++)
                {
                    if (parameters[i].ParameterType.IsByRef)
                    {
                        outputParamPositions.Add(i);
                    }
                }
 
                var outputPos = outputParamPositions.ToArray();
                outputParameterCount = outputPos.Length;
 
                InvokeDelegate lambda = delegate (object target, object[] inputs, object[] outputs)
                {
                    object[] inputsLocal = null;
                    if (inputCount > 0)
                    {
                        inputsLocal = new object[inputCount];
                        for (var i = 0; i < inputCount; i++)
                        {
                            inputsLocal[i] = inputs[i];
                        }
                    }
                    object result = null;
                    try
                    {
                        if (returnsValue)
                        {
                            result = method.Invoke(target, inputsLocal);
                        }
                        else
                        {
                            method.Invoke(target, inputsLocal);
                        }
                    }
                    catch (TargetInvocationException tie)
                    {
                        Exception actualException = tie.InnerException;
                        ExceptionDispatchInfo.Capture(actualException).Throw(); // Keep original call stack
                    }
                    for (var i = 0; i < outputPos.Length; i++)
                    {
                        outputs[i] = inputs[outputPos[i]];
                    }
 
                    return result;
                };
 
                return lambda;
            }
 
            internal InvokeDelegate GenerateInvokeDelegateInternalWithExpressions(MethodInfo method, out int inputParameterCount, out int outputParameterCount)
            {
                inputParameterCount = 0;
                outputParameterCount = 0;
                ParameterInfo[] parameters = method.GetParameters();
                bool returnsValue = method.ReturnType != typeof(void);
                bool returnsValueType = method.ReturnType.IsValueType;
 
                var targetParam = Expression.Parameter(typeof(object), "target");
                var inputsParam = Expression.Parameter(typeof(object[]), "inputs");
                var outputsParam = Expression.Parameter(typeof(object[]), "outputs");
 
                List<ParameterExpression> variables = new();
                var result = Expression.Variable(typeof(object), "result");
                variables.Add(result);
 
                List<(Type ParameterType, ParameterExpression OutputExpression)> outputVariables = new();
                List<ParameterExpression> invocationParameters = new();
                List<Expression> expressions = new();
 
                for (int i = 0; i < parameters.Length; i++)
                {
                    Type variableType = parameters[i].ParameterType.IsByRef
                        ? parameters[i].ParameterType.GetElementType()
                        : parameters[i].ParameterType;
                    ParameterExpression variable = Expression.Variable(variableType, $"p{i}");
 
                    if (ServiceReflector.FlowsIn(parameters[i]))
                    {
                        expressions.Add(Expression.Assign(variable, Expression.Convert(Expression.ArrayIndex(inputsParam, Expression.Constant(inputParameterCount)), variableType)));
                        inputParameterCount++;
                    }
 
                    if (ServiceReflector.FlowsOut(parameters[i]))
                    {
                        outputParameterCount++;
                        outputVariables.Add((variableType, variable));
                    }
 
                    variables.Add(variable);
                    invocationParameters.Add(variable);
                }
 
                var castTargetParam = Expression.Convert(targetParam, method.DeclaringType);
 
                if (returnsValue)
                {
                    if (returnsValueType)
                    {
                        expressions.Add(Expression.Assign(result, Expression.Convert(Expression.Call(castTargetParam, method, invocationParameters), typeof(object))));
                    }
                    else
                    {
                        expressions.Add(Expression.Assign(result, Expression.Call(castTargetParam, method, invocationParameters)));
                    }
                }
                else
                {
                    expressions.Add(Expression.Call(castTargetParam, method, invocationParameters));
                    expressions.Add(Expression.Assign(result, Expression.Constant(null, typeof(object))));
                }
 
                int j = 0;
                foreach (var outputVariable in outputVariables)
                {
                    if (outputVariable.ParameterType.IsValueType)
                    {
                        expressions.Add(Expression.Assign(
                            Expression.ArrayAccess(outputsParam, Expression.Constant(j)),
                            Expression.Convert(outputVariable.OutputExpression, typeof(object))));
                    }
                    else
                    {
                        expressions.Add(Expression.Assign(
                            Expression.ArrayAccess(outputsParam, Expression.Constant(j)),
                            outputVariable.OutputExpression));
                    }
                    j++;
                }
 
                expressions.Add(result);
 
                BlockExpression finalBlock = Expression.Block(variables: variables, expressions: expressions);
 
                Expression<InvokeDelegate> lambda = Expression.Lambda<InvokeDelegate>(
                    finalBlock,
                    targetParam,
                    inputsParam,
                    outputsParam);
 
                return lambda.Compile();
            }
        }
    }
}