File: Microsoft\CSharp\RuntimeBinder\ComInterop\DynamicVariantExtensions.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;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using Microsoft.CSharp.RuntimeBinder.ComInterop;

namespace Microsoft.CSharp.RuntimeBinder.ComInterop
{
    internal static class DynamicVariantExtensions
    {
        /// <summary>
        /// Primitive types are the basic COM types. It includes valuetypes like ints, but also reference types
        /// like BStrs. It does not include composite types like arrays and user-defined COM types (IUnknown/IDispatch).
        /// </summary>
        public static bool IsPrimitiveType(this VarEnum varEnum)
        {
            switch (varEnum)
            {
                case VarEnum.VT_I1:
                case VarEnum.VT_I2:
                case VarEnum.VT_I4:
                case VarEnum.VT_I8:
                case VarEnum.VT_UI1:
                case VarEnum.VT_UI2:
                case VarEnum.VT_UI4:
                case VarEnum.VT_UI8:
                case VarEnum.VT_INT:
                case VarEnum.VT_UINT:
                case VarEnum.VT_BOOL:
                case VarEnum.VT_ERROR:
                case VarEnum.VT_R4:
                case VarEnum.VT_R8:
                case VarEnum.VT_DECIMAL:
                case VarEnum.VT_CY:
                case VarEnum.VT_DATE:
                case VarEnum.VT_BSTR:
                    return true;
            }

            return false;
        }

        public static void SetAsIConvertible(this ref ComVariant variant, IConvertible value)
        {
            Debug.Assert(variant.VarType == VarEnum.VT_EMPTY); // The setter can only be called once as VariantClear might be needed otherwise

            TypeCode tc = value.GetTypeCode();
            CultureInfo ci = CultureInfo.CurrentCulture;

            switch (tc)
            {
                case TypeCode.Empty: break;
                case TypeCode.Object: variant = ComVariant.CreateRaw(VarEnum.VT_UNKNOWN, Marshal.GetIUnknownForObject(value)); break;
                case TypeCode.DBNull: variant = ComVariant.Null; break;
                case TypeCode.Boolean: variant = ComVariant.Create<bool>(value.ToBoolean(ci)); break;
                case TypeCode.Char: variant = ComVariant.Create<ushort>(value.ToChar(ci)); break;
                case TypeCode.SByte: variant = ComVariant.Create<sbyte>(value.ToSByte(ci)); break;
                case TypeCode.Byte: variant = ComVariant.Create<byte>(value.ToByte(ci)); break;
                case TypeCode.Int16: variant = ComVariant.Create(value.ToInt16(ci)); break;
                case TypeCode.UInt16: variant = ComVariant.Create(value.ToUInt16(ci)); break;
                case TypeCode.Int32: variant = ComVariant.Create(value.ToInt32(ci)); break;
                case TypeCode.UInt32: variant = ComVariant.Create(value.ToUInt32(ci)); break;
                case TypeCode.Int64: variant = ComVariant.Create(value.ToInt64(ci)); break;
                case TypeCode.UInt64: variant = ComVariant.Create(value.ToUInt64(ci)); break;
                case TypeCode.Single: variant = ComVariant.Create(value.ToSingle(ci)); break;
                case TypeCode.Double: variant = ComVariant.Create(value.ToDouble(ci)); break;
                case TypeCode.Decimal: variant = ComVariant.Create(value.ToDecimal(ci)); break;
                case TypeCode.DateTime: variant = ComVariant.Create(value.ToDateTime(ci)); break;
                case TypeCode.String: variant = ComVariant.Create(new BStrWrapper(value.ToString(ci))); break;

                default:
                    throw new NotSupportedException();
            }
        }
        // VT_I1

        public static void SetAsByrefI1(ref this ComVariant variant, ref sbyte value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_I1);
        }

        // VT_I2

        public static void SetAsByrefI2(ref this ComVariant variant, ref short value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_I2);
        }

        // VT_I4

        public static void SetAsByrefI4(ref this ComVariant variant, ref int value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_I4);
        }

        // VT_I8

        public static void SetAsByrefI8(ref this ComVariant variant, ref long value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_I8);
        }

        // VT_UI1

        public static void SetAsByrefUi1(ref this ComVariant variant, ref byte value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_UI1);
        }

        // VT_UI2

        public static void SetAsByrefUi2(ref this ComVariant variant, ref ushort value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_UI2);
        }

        // VT_UI4

        public static void SetAsByrefUi4(ref this ComVariant variant, ref uint value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_UI4);
        }

        // VT_UI8

        public static void SetAsByrefUi8(ref this ComVariant variant, ref ulong value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_UI8);
        }

        // VT_INT

        public static void SetAsByrefInt(ref this ComVariant variant, ref int value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_INT);
        }

        // VT_UINT

        public static void SetAsByrefUint(ref this ComVariant variant, ref uint value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_UINT);
        }

        // VT_BOOL

        public static void SetAsByrefBool(ref this ComVariant variant, ref short value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_BOOL);
        }

        // VT_ERROR

        public static void SetAsByrefError(ref this ComVariant variant, ref int value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_ERROR);
        }

        // VT_R4

        public static void SetAsByrefR4(ref this ComVariant variant, ref float value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_R4);
        }

        // VT_R8

        public static void SetAsByrefR8(ref this ComVariant variant, ref double value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_R8);
        }

        // VT_DECIMAL

        public static void SetAsByrefDecimal(ref this ComVariant variant, ref decimal value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_DECIMAL);
        }

        // VT_CY

        public static void SetAsByrefCy(ref this ComVariant variant, ref long value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_CY);
        }

        // VT_DATE

        public static void SetAsByrefDate(ref this ComVariant variant, ref double value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_DATE);
        }

        // VT_BSTR

        public static void SetAsByrefBstr(ref this ComVariant variant, ref IntPtr value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_BSTR);
        }

        // VT_UNKNOWN

        public static void SetAsByrefUnknown(ref this ComVariant variant, ref IntPtr value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_UNKNOWN);
        }

        // VT_DISPATCH

        public static void SetAsByrefDispatch(ref this ComVariant variant, ref IntPtr value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_DISPATCH);
        }

        private static unsafe void SetAsByref<T>(ref this ComVariant variant, ref T value, VarEnum type)
        {
            Debug.Assert(variant.VarType == VarEnum.VT_EMPTY); // The setter can only be called once as VariantClear might be needed otherwise
            variant = ComVariant.CreateRaw(type | VarEnum.VT_BYREF, (nint)Unsafe.AsPointer(ref value));
        }

        public static void SetAsByrefVariant(ref this ComVariant variant, ref ComVariant value)
        {
            variant.SetAsByref(ref value, VarEnum.VT_VARIANT);
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct Record
        {
            public IntPtr _record;
            public IntPtr _recordInfo;
        }

        // constructs a ByRef variant to pass contents of another variant ByRef.
        public static unsafe void SetAsByrefVariantIndirect(ref this ComVariant variant, ref ComVariant value)
        {
            Debug.Assert(variant.VarType == VarEnum.VT_EMPTY); // The setter can only be called once as VariantClear might be needed otherwise
            Debug.Assert((value.VarType & VarEnum.VT_BYREF) == 0, "double indirection");

            switch (value.VarType)
            {
                case VarEnum.VT_EMPTY:
                case VarEnum.VT_NULL:
                    // these cannot combine with VT_BYREF. Should try passing as a variant reference
                    variant.SetAsByrefVariant(ref value);
                    return;
                case VarEnum.VT_RECORD:
                    variant = ComVariant.CreateRaw(value.VarType | VarEnum.VT_BYREF, value.GetRawDataRef<Record>());
                    break;
                case VarEnum.VT_DECIMAL:
                    // The DECIMAL value in an OLE Variant is stored at the start of the structure.
                    variant = ComVariant.CreateRaw(value.VarType | VarEnum.VT_BYREF, (nint)Unsafe.AsPointer(ref value));
                    break;
                default:
                    variant = ComVariant.CreateRaw(value.VarType | VarEnum.VT_BYREF, (nint)Unsafe.AsPointer(ref value.GetRawDataRef<nint>()));
                    break;
            }
        }

        internal static System.Reflection.MethodInfo GetByrefSetter(VarEnum varType)
        {
            switch (varType)
            {
                case VarEnum.VT_I1: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefI1));
                case VarEnum.VT_I2: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefI2));
                case VarEnum.VT_I4: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefI4));
                case VarEnum.VT_I8: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefI8));
                case VarEnum.VT_UI1: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefUi1));
                case VarEnum.VT_UI2: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefUi2));
                case VarEnum.VT_UI4: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefUi4));
                case VarEnum.VT_UI8: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefUi8));
                case VarEnum.VT_INT: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefInt));
                case VarEnum.VT_UINT: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefUint));
                case VarEnum.VT_BOOL: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefBool));
                case VarEnum.VT_ERROR: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefError));
                case VarEnum.VT_R4: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefR4));
                case VarEnum.VT_R8: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefR8));
                case VarEnum.VT_DECIMAL: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefDecimal));
                case VarEnum.VT_CY: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefCy));
                case VarEnum.VT_DATE: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefDate));
                case VarEnum.VT_BSTR: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefBstr));
                case VarEnum.VT_UNKNOWN: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefUnknown));
                case VarEnum.VT_DISPATCH: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefDispatch));

                case VarEnum.VT_VARIANT:
                    return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefVariant));
                case VarEnum.VT_RECORD:
                case VarEnum.VT_ARRAY:
                    return typeof(DynamicVariantExtensions).GetMethod(nameof(SetAsByrefVariantIndirect));

                default:
                    throw new NotSupportedException();
            }
        }

        public static void SetI1(this ref ComVariant variant, sbyte value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetUi1(this ref ComVariant variant, byte value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetI2(this ref ComVariant variant, short value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetUi2(this ref ComVariant variant, ushort value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetI4(this ref ComVariant variant, int value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetUi4(this ref ComVariant variant, uint value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetI8(this ref ComVariant variant, long value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetUi8(this ref ComVariant variant, ulong value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetInt(this ref ComVariant variant, int value)
        {
            variant = ComVariant.CreateRaw(VarEnum.VT_INT, value);
        }

        public static void SetUint(this ref ComVariant variant, uint value)
        {
            variant = ComVariant.CreateRaw(VarEnum.VT_UINT, value);
        }

        public static void SetBool(this ref ComVariant variant, bool value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetR4(this ref ComVariant variant, float value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetR8(this ref ComVariant variant, double value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetDecimal(this ref ComVariant variant, decimal value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetDate(this ref ComVariant variant, DateTime value)
        {
            variant = ComVariant.Create(value);
        }

        public static void SetBstr(this ref ComVariant variant, string value)
        {
            variant = ComVariant.Create(new BStrWrapper(value));
        }

        public static void SetUnknown(this ref ComVariant variant, object? value)
        {
            variant = ComVariant.CreateRaw(VarEnum.VT_UNKNOWN, value is null ? IntPtr.Zero : Marshal.GetIUnknownForObject(value));
        }

        public static void SetDispatch(this ref ComVariant variant, object? value)
        {
            variant = ComVariant.CreateRaw(VarEnum.VT_DISPATCH, value is null ? IntPtr.Zero : Marshal.GetIDispatchForObject(value));
        }

        public static void SetError(this ref ComVariant variant, int value)
        {
            variant = ComVariant.CreateRaw(VarEnum.VT_ERROR, value);
        }

        public static void SetCy(this ref ComVariant variant, decimal value)
        {
            variant = ComVariant.CreateRaw(VarEnum.VT_CY, decimal.ToOACurrency(value));
        }

        public static unsafe void SetVariant(this ref ComVariant variant, object value)
        {
            Debug.Assert(variant.VarType == VarEnum.VT_EMPTY); // The setter can only be called once as VariantClear might be needed otherwise
            if (value != null)
            {
                UnsafeMethods.InitVariantForObject(value, ref variant);
            }
        }

        internal static System.Reflection.MethodInfo GetSetter(VarEnum varType)
        {
            switch (varType)
            {
                case VarEnum.VT_I1: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetI1), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_I2: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetI2), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_I4: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetI4), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_I8: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetI8), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_UI1: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetUi1), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_UI2: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetUi2), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_UI4: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetUi4), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_UI8: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetUi8), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_INT: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetInt), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_UINT: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetUint), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_BOOL: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetBool), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_ERROR: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetError), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_R4: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetR4), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_R8: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetR8), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_DECIMAL: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetDecimal), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_CY: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetCy), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_DATE: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetDate), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_BSTR: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetBstr), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_UNKNOWN: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetUnknown), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                case VarEnum.VT_DISPATCH: return typeof(DynamicVariantExtensions).GetMethod(nameof(SetDispatch), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);

                case VarEnum.VT_VARIANT:
                case VarEnum.VT_RECORD:
                case VarEnum.VT_ARRAY:
                    return typeof(DynamicVariantExtensions).GetMethod(nameof(SetVariant), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);

                default:
                    throw new NotSupportedException();
            }
        }
    }
}