File: Microsoft\CSharp\RuntimeBinder\ComInterop\VariantBuilder.cs
Web Access
Project: src\src\runtime\src\libraries\Microsoft.CSharp\src\Microsoft.CSharp.csproj (Microsoft.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

namespace Microsoft.CSharp.RuntimeBinder.ComInterop
{
    /// <summary>
    /// VariantBuilder handles packaging of arguments into an ComVariant for a call to IDispatch.Invoke
    /// </summary>
    internal sealed class VariantBuilder
    {
        private MemberExpression _variant;
        private readonly ArgBuilder _argBuilder;
        private readonly VarEnum _targetComType;
        internal ParameterExpression TempVariable { get; private set; }

        internal VariantBuilder(VarEnum targetComType, ArgBuilder builder)
        {
            _targetComType = targetComType;
            _argBuilder = builder;
        }

        internal bool IsByRef
        {
            get { return (_targetComType & VarEnum.VT_BYREF) != 0; }
        }

        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        internal Expression InitializeArgumentVariant(MemberExpression variant, Expression parameter)
        {
            //NOTE: we must remember our variant
            //the reason is that argument order does not map exactly to the order of variants for invoke
            //and when we are doing clean-up we must be sure we are cleaning the variant we have initialized.

            _variant = variant;

            if (IsByRef)
            {
                // temp = argument
                // paramVariants._elementN.SetAsByrefT(ref temp)
                Debug.Assert(TempVariable == null);
                Expression argExpr = _argBuilder.MarshalToRef(parameter);

                TempVariable = Expression.Variable(argExpr.Type, null);
                return Expression.Block(
                    Expression.Assign(TempVariable, argExpr),
                    Expression.Call(
                        DynamicVariantExtensions.GetByrefSetter(_targetComType & ~VarEnum.VT_BYREF),
                        variant,
                        TempVariable
                    )
                );
            }

            Expression argument = _argBuilder.Marshal(parameter);

            // we are forced to special case ConvertibleArgBuilder since it does not have
            // a corresponding _targetComType.
            if (_argBuilder is ConvertibleArgBuilder)
            {
                return Expression.Call(
                    typeof(DynamicVariantExtensions).GetMethod(nameof(DynamicVariantExtensions.SetAsIConvertible)),
                    variant,
                    argument
                );
            }

            if (_targetComType.IsPrimitiveType() ||
               (_targetComType == VarEnum.VT_DISPATCH) ||
               (_targetComType == VarEnum.VT_UNKNOWN) ||
               (_targetComType == VarEnum.VT_VARIANT) ||
               (_targetComType == VarEnum.VT_RECORD) ||
               (_targetComType == VarEnum.VT_ARRAY))
            {
                // paramVariants._elementN.AsT = (cast)argN
                return Expression.Call(null,
                    DynamicVariantExtensions.GetSetter(_targetComType),
                    variant,
                    argument);
            }

            switch (_targetComType)
            {
                case VarEnum.VT_EMPTY:
                    return null;

                case VarEnum.VT_NULL:
                    // paramVariants._elementN = ComVariant.Null;
                    return Expression.Assign(variant, Expression.Property(null, typeof(ComVariant).GetProperty(nameof(ComVariant.Null), BindingFlags.Public | BindingFlags.Static)));

                default:
                    Debug.Fail("Unexpected VarEnum");
                    return null;
            }
        }

        private static Expression Release(Expression pUnk)
        {
            return Expression.Call(typeof(UnsafeMethods).GetMethod(nameof(UnsafeMethods.IUnknownReleaseNotZero)), pUnk);
        }

        internal Expression Clear()
        {
            if (IsByRef)
            {
                if (_argBuilder is StringArgBuilder)
                {
                    Debug.Assert(TempVariable != null);
                    return Expression.Call(typeof(Marshal).GetMethod(nameof(Marshal.FreeBSTR)), TempVariable);
                }

                if (_argBuilder is DispatchArgBuilder)
                {
                    Debug.Assert(TempVariable != null);
                    return Release(TempVariable);
                }

                if (_argBuilder is UnknownArgBuilder)
                {
                    Debug.Assert(TempVariable != null);
                    return Release(TempVariable);
                }

                if (_argBuilder is VariantArgBuilder)
                {
                    Debug.Assert(TempVariable != null);
                    return Expression.Call(TempVariable, typeof(ComVariant).GetMethod(nameof(ComVariant.Dispose)));
                }
                return null;
            }

            switch (_targetComType)
            {
                case VarEnum.VT_EMPTY:
                case VarEnum.VT_NULL:
                    return null;

                case VarEnum.VT_BSTR:
                case VarEnum.VT_UNKNOWN:
                case VarEnum.VT_DISPATCH:
                case VarEnum.VT_ARRAY:
                case VarEnum.VT_RECORD:
                case VarEnum.VT_VARIANT:
                    // paramVariants._elementN.Dispose()
                    return Expression.Call(_variant, typeof(ComVariant).GetMethod(nameof(ComVariant.Dispose)));

                default:
                    Debug.Assert(_targetComType.IsPrimitiveType(), "Unexpected VarEnum");
                    return null;
            }
        }

        internal Expression UpdateFromReturn(Expression parameter)
        {
            if (TempVariable == null)
            {
                return null;
            }
            return Expression.Assign(
                parameter,
                Helpers.Convert(
                    _argBuilder.UnmarshalFromRef(TempVariable),
                    parameter.Type
                )
            );
        }
    }
}