Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
VarEnumSelector.cs552 linesDownload Raw Back to ComInterop
1// Licensed to the .NET Foundation under one or more agreements.2// The .NET Foundation licenses this file to you under the MIT license.3 4#pragma warning disable 618 // The *Wrapper classes for COM are obsolete5 6using System;7using System.Collections.Generic;8using System.Diagnostics;9using System.Reflection;10using System.Management.Automation.InteropServices;11using System.Runtime.InteropServices;12 13namespace System.Management.Automation.ComInterop14{15    /// <summary>16    /// If a managed user type (as opposed to a primitive type or a COM object) is passed as an argument to a COM call, we need17    /// to determine the VarEnum type we will marshal it as. We have the following options:18    /// 1. Raise an exception. Languages with their own version of primitive types would not be able to call19    ///    COM methods using the language's types (for eg. strings in IronRuby are not System.String). An explicit20    ///    cast would be needed.21    /// 2. We could marshal it as VT_DISPATCH. Then COM code will be able to access all the APIs in a late-bound manner,22    ///    but old COM components will probably malfunction if they expect a primitive type.23    /// 3. We could guess which primitive type is the closest match. This will make COM components be as easily24    ///    accessible as .NET methods.25    /// 4. We could use the type library to check what the expected type is. However, the type library may not be available.26    ///27    /// VarEnumSelector implements option # 3.28    /// </summary>29    internal class VarEnumSelector30    {31        private static readonly Dictionary<VarEnum, Type> s_comToManagedPrimitiveTypes = CreateComToManagedPrimitiveTypes();32        private static readonly IList<IList<VarEnum>> s_comPrimitiveTypeFamilies = CreateComPrimitiveTypeFamilies();33 34        internal VarEnumSelector(Type[] explicitArgTypes)35        {36            VariantBuilders = new VariantBuilder[explicitArgTypes.Length];37 38            for (int i = 0; i < explicitArgTypes.Length; i++)39            {40                VariantBuilders[i] = GetVariantBuilder(explicitArgTypes[i]);41            }42        }43 44        internal VariantBuilder[] VariantBuilders { get; }45 46        internal static Type GetTypeForVarEnum(VarEnum vt)47        {48            Type type;49 50            switch (vt)51            {52                // VarEnums which can be used in VARIANTs, but which cannot occur in a TYPEDESC53                case VarEnum.VT_EMPTY:54                case VarEnum.VT_NULL:55                case VarEnum.VT_RECORD:56                    type = typeof(void);57                    break;58 59                // VarEnums which are not used in VARIANTs, but which can occur in a TYPEDESC60                case VarEnum.VT_VOID:61                    type = typeof(void);62                    break;63 64                case VarEnum.VT_HRESULT:65                    type = typeof(int);66                    break;67 68                case ((VarEnum)37): // VT_INT_PTR:69                    type = typeof(IntPtr);70                    break;71 72                case ((VarEnum)38): // VT_UINT_PTR:73                    type = typeof(UIntPtr);74                    break;75 76                case VarEnum.VT_SAFEARRAY:77                case VarEnum.VT_CARRAY:78                    type = typeof(Array);79                    break;80 81                case VarEnum.VT_LPSTR:82                case VarEnum.VT_LPWSTR:83                    type = typeof(string);84                    break;85 86                case VarEnum.VT_PTR:87                case VarEnum.VT_USERDEFINED:88                    type = typeof(object);89                    break;90 91                // For VarEnums that can be used in VARIANTs and well as TYPEDESCs, just use VarEnumSelector92                default:93                    type = VarEnumSelector.GetManagedMarshalType(vt);94                    break;95            }96 97            return type;98        }99 100        /// <summary>101        /// Gets the managed type that an object needs to be converted to in order for it to be able102        /// to be represented as a Variant.103        ///104        /// In general, there is a many-to-many mapping between Type and VarEnum. However, this method105        /// returns a simple mapping that is needed for the current implementation. The reason for the106        /// many-to-many relation is:107        /// 1. Int32 maps to VT_I4 as well as VT_ERROR, and Decimal maps to VT_DECIMAL and VT_CY. However,108        ///    this changes if you throw the wrapper types into the mix.109        /// 2. There is no Type to represent COM types. __ComObject is a private type, and Object is too110        ///    general.111        /// </summary>112        internal static Type GetManagedMarshalType(VarEnum varEnum)113        {114            Debug.Assert((varEnum & VarEnum.VT_BYREF) == 0);115 116            if (varEnum == VarEnum.VT_CY)117            {118                return typeof(CurrencyWrapper);119            }120 121            if (Variant.IsPrimitiveType(varEnum))122            {123                return s_comToManagedPrimitiveTypes[varEnum];124            }125 126            switch (varEnum)127            {128                case VarEnum.VT_EMPTY:129                case VarEnum.VT_NULL:130                case VarEnum.VT_UNKNOWN:131                case VarEnum.VT_DISPATCH:132                case VarEnum.VT_VARIANT:133                    return typeof(object);134 135                case VarEnum.VT_ERROR:136                    return typeof(ErrorWrapper);137 138                default:139                    throw Error.UnexpectedVarEnum(varEnum);140            }141        }142 143        private static Dictionary<VarEnum, Type> CreateComToManagedPrimitiveTypes()144        {145            Dictionary<VarEnum, Type> dict = new Dictionary<VarEnum, Type>()146            {147                { VarEnum.VT_I1,        typeof(sbyte) },148                { VarEnum.VT_I2,        typeof(Int16) },149                { VarEnum.VT_I4,        typeof(Int32) },150                { VarEnum.VT_I8,        typeof(Int64) },151                { VarEnum.VT_UI1,       typeof(byte) },152                { VarEnum.VT_UI2,       typeof(UInt16) },153                { VarEnum.VT_UI4,       typeof(UInt32) },154                { VarEnum.VT_UI8,       typeof(UInt64) },155                { VarEnum.VT_INT,       typeof(Int32) },156                { VarEnum.VT_UINT,      typeof(UInt32) },157                { VarEnum.VT_PTR,       typeof(IntPtr) },158                { VarEnum.VT_BOOL,      typeof(bool) },159                { VarEnum.VT_R4,        typeof(float) },160                { VarEnum.VT_R8,        typeof(double) },161                { VarEnum.VT_DECIMAL,   typeof(decimal) },162                { VarEnum.VT_DATE,      typeof(DateTime) },163                { VarEnum.VT_BSTR,      typeof(string) },164                { VarEnum.VT_CLSID,     typeof(Guid) },165                { VarEnum.VT_CY,        typeof(CurrencyWrapper) },166                { VarEnum.VT_ERROR,     typeof(ErrorWrapper) },167            };168 169            return dict;170        }171 172        #region Primitive COM types173 174        /// <summary>175        /// Creates a family of COM types such that within each family, there is a completely non-lossy176        /// conversion from a type to an earlier type in the family.177        /// </summary>178        private static IList<IList<VarEnum>> CreateComPrimitiveTypeFamilies()179        {180            VarEnum[][] typeFamilies = new VarEnum[][] {181                new VarEnum[] { VarEnum.VT_I8, VarEnum.VT_I4, VarEnum.VT_I2, VarEnum.VT_I1 },182                new VarEnum[] { VarEnum.VT_UI8, VarEnum.VT_UI4, VarEnum.VT_UI2, VarEnum.VT_UI1 },183                new VarEnum[] { VarEnum.VT_INT },184                new VarEnum[] { VarEnum.VT_UINT },185                new VarEnum[] { VarEnum.VT_BOOL },186                new VarEnum[] { VarEnum.VT_DATE },187                new VarEnum[] { VarEnum.VT_R8, VarEnum.VT_R4 },188                new VarEnum[] { VarEnum.VT_DECIMAL },189                new VarEnum[] { VarEnum.VT_BSTR },190 191                // wrappers192                new VarEnum[] { VarEnum.VT_CY },193                new VarEnum[] { VarEnum.VT_ERROR },194            };195 196            return typeFamilies;197        }198 199        /// <summary>200        /// Get the (one representative type for each) primitive type families that the argument can be converted to.201        /// </summary>202        private static List<VarEnum> GetConversionsToComPrimitiveTypeFamilies(Type argumentType)203        {204            List<VarEnum> compatibleComTypes = new List<VarEnum>();205 206            foreach (IList<VarEnum> typeFamily in s_comPrimitiveTypeFamilies)207            {208                foreach (VarEnum candidateType in typeFamily)209                {210                    Type candidateManagedType = s_comToManagedPrimitiveTypes[candidateType];211                    if (TypeUtils.IsImplicitlyConvertible(argumentType, candidateManagedType, true))212                    {213                        compatibleComTypes.Add(candidateType);214                        // Move on to the next type family. We need at most one type from each family215                        break;216                    }217                }218            }219            return compatibleComTypes;220        }221 222        /// <summary>223        /// If there is more than one type family that the argument can be converted to, we will throw a224        /// AmbiguousMatchException instead of randomly picking a winner.225        /// </summary>226        private static void CheckForAmbiguousMatch(Type argumentType, List<VarEnum> compatibleComTypes)227        {228            if (compatibleComTypes.Count <= 1)229            {230                return;231            }232 233            string typeNames = string.Empty;234            for (int i = 0; i < compatibleComTypes.Count; i++)235            {236                string typeName = s_comToManagedPrimitiveTypes[compatibleComTypes[i]].Name;237                if (i == (compatibleComTypes.Count - 1))238                {239                    typeNames += " and ";240                }241                else if (i != 0)242                {243                    typeNames += ", ";244                }245                typeNames += typeName;246            }247 248            throw Error.AmbiguousConversion(argumentType.Name, typeNames);249        }250 251        private static bool TryGetPrimitiveComType(Type argumentType, out VarEnum primitiveVarEnum)252        {253            switch (Type.GetTypeCode(argumentType))254            {255                case TypeCode.Boolean:256                    primitiveVarEnum = VarEnum.VT_BOOL;257                    return true;258                case TypeCode.Char:259                    primitiveVarEnum = VarEnum.VT_UI2;260                    return true;261                case TypeCode.SByte:262                    primitiveVarEnum = VarEnum.VT_I1;263                    return true;264                case TypeCode.Byte:265                    primitiveVarEnum = VarEnum.VT_UI1;266                    return true;267                case TypeCode.Int16:268                    primitiveVarEnum = VarEnum.VT_I2;269                    return true;270                case TypeCode.UInt16:271                    primitiveVarEnum = VarEnum.VT_UI2;272                    return true;273                case TypeCode.Int32:274                    primitiveVarEnum = VarEnum.VT_I4;275                    return true;276                case TypeCode.UInt32:277                    primitiveVarEnum = VarEnum.VT_UI4;278                    return true;279                case TypeCode.Int64:280                    primitiveVarEnum = VarEnum.VT_I8;281                    return true;282                case TypeCode.UInt64:283                    primitiveVarEnum = VarEnum.VT_UI8;284                    return true;285                case TypeCode.Single:286                    primitiveVarEnum = VarEnum.VT_R4;287                    return true;288                case TypeCode.Double:289                    primitiveVarEnum = VarEnum.VT_R8;290                    return true;291                case TypeCode.Decimal:292                    primitiveVarEnum = VarEnum.VT_DECIMAL;293                    return true;294                case TypeCode.DateTime:295                    primitiveVarEnum = VarEnum.VT_DATE;296                    return true;297                case TypeCode.String:298                    primitiveVarEnum = VarEnum.VT_BSTR;299                    return true;300            }301 302            if (argumentType == typeof(CurrencyWrapper))303            {304                primitiveVarEnum = VarEnum.VT_CY;305                return true;306            }307 308            if (argumentType == typeof(ErrorWrapper))309            {310                primitiveVarEnum = VarEnum.VT_ERROR;311                return true;312            }313 314            if (argumentType == typeof(IntPtr))315            {316                primitiveVarEnum = VarEnum.VT_INT;317                return true;318            }319 320            if (argumentType == typeof(UIntPtr))321            {322                primitiveVarEnum = VarEnum.VT_UINT;323                return true;324            }325 326            primitiveVarEnum = VarEnum.VT_VOID; // error327            return false;328        }329 330        /// <summary>331        /// Is there a unique primitive type that has the best conversion for the argument.332        /// </summary>333        private static bool TryGetPrimitiveComTypeViaConversion(Type argumentType, out VarEnum primitiveVarEnum)334        {335            // Look for a unique type family that the argument can be converted to.336            List<VarEnum> compatibleComTypes = GetConversionsToComPrimitiveTypeFamilies(argumentType);337            CheckForAmbiguousMatch(argumentType, compatibleComTypes);338            if (compatibleComTypes.Count == 1)339            {340                primitiveVarEnum = compatibleComTypes[0];341                return true;342            }343 344            primitiveVarEnum = VarEnum.VT_VOID; // error345            return false;346        }347 348        #endregion349 350        // Type.InvokeMember tries to marshal objects as VT_DISPATCH, and falls back to VT_UNKNOWN351        // VT_RECORD here just indicates that we have user defined type.352        // We will try VT_DISPATCH and then call GetNativeVariantForObject.353        private const VarEnum VT_DEFAULT = VarEnum.VT_RECORD;354 355        private static VarEnum GetComType(ref Type argumentType)356        {357            if (argumentType == typeof(Missing))358            {359                //actual variant type will be VT_ERROR | E_PARAMNOTFOUND360                return VarEnum.VT_RECORD;361            }362 363            if (argumentType.IsArray)364            {365                //actual variant type will be VT_ARRAY | VT_<ELEMENT_TYPE>366                return VarEnum.VT_ARRAY;367            }368 369            if (argumentType == typeof(UnknownWrapper))370            {371                return VarEnum.VT_UNKNOWN;372            }373 374            if (argumentType == typeof(DispatchWrapper))375            {376                return VarEnum.VT_DISPATCH;377            }378 379            if (argumentType == typeof(VariantWrapper))380            {381                return VarEnum.VT_VARIANT;382            }383 384            if (argumentType == typeof(BStrWrapper))385            {386                return VarEnum.VT_BSTR;387            }388 389            if (argumentType == typeof(ErrorWrapper))390            {391                return VarEnum.VT_ERROR;392            }393 394            if (argumentType == typeof(CurrencyWrapper))395            {396                return VarEnum.VT_CY;397            }398 399            // Many languages require an explicit cast for an enum to be used as the underlying type.400            // However, we want to allow this conversion for COM without requiring an explicit cast401            // so that enums from interop assemblies can be used as arguments.402            if (argumentType.IsEnum)403            {404                argumentType = Enum.GetUnderlyingType(argumentType);405                return GetComType(ref argumentType);406            }407 408            // COM cannot express valuetype nulls so we will convert to underlying type409            // it will throw if there is no value410            if (argumentType.IsNullableType())411            {412                argumentType = TypeUtils.GetNonNullableType(argumentType);413                return GetComType(ref argumentType);414            }415 416            //generic types cannot be exposed to COM so they do not implement COM interfaces.417            if (argumentType.IsGenericType)418            {419                return VarEnum.VT_UNKNOWN;420            }421 422            if (TryGetPrimitiveComType(argumentType, out VarEnum primitiveVarEnum))423            {424                return primitiveVarEnum;425            }426 427            // We could not find a way to marshal the type as a specific COM type428            return VT_DEFAULT;429        }430 431        /// <summary>432        /// Get the COM Variant type that argument should be marshalled as for a call to COM.433        /// </summary>434        private static VariantBuilder GetVariantBuilder(Type argumentType)435        {436            //argumentType is coming from MarshalType, null means the dynamic object holds437            //a null value and not byref438            if (argumentType == null)439            {440                return new VariantBuilder(VarEnum.VT_EMPTY, new NullArgBuilder());441            }442 443            if (argumentType == typeof(DBNull))444            {445                return new VariantBuilder(VarEnum.VT_NULL, new NullArgBuilder());446            }447 448            ArgBuilder argBuilder;449 450            if (argumentType.IsByRef)451            {452                Type elementType = argumentType.GetElementType();453 454                VarEnum elementVarEnum;455                if (elementType == typeof(object) || elementType == typeof(DBNull))456                {457                    //no meaningful value to pass ByRef.458                    //perhaps the callee will replace it with something.459                    //need to pass as a variant reference460                    elementVarEnum = VarEnum.VT_VARIANT;461                }462                else463                {464                    elementVarEnum = GetComType(ref elementType);465                }466 467                argBuilder = GetSimpleArgBuilder(elementType, elementVarEnum);468                return new VariantBuilder(elementVarEnum | VarEnum.VT_BYREF, argBuilder);469            }470 471            VarEnum varEnum = GetComType(ref argumentType);472            argBuilder = GetByValArgBuilder(argumentType, ref varEnum);473 474            return new VariantBuilder(varEnum, argBuilder);475        }476 477        // This helper is called when we are looking for a ByVal marshalling478        // In a ByVal case we can take into account conversions or IConvertible if all other479        // attempts to find marshalling type failed480        private static ArgBuilder GetByValArgBuilder(Type elementType, ref VarEnum elementVarEnum)481        {482            // If VT indicates that marshalling type is unknown.483            if (elementVarEnum == VT_DEFAULT)484            {485                // Trying to find a conversion.486                if (TryGetPrimitiveComTypeViaConversion(elementType, out VarEnum convertibleTo))487                {488                    elementVarEnum = convertibleTo;489                    Type marshalType = GetManagedMarshalType(elementVarEnum);490                    return new ConversionArgBuilder(elementType, GetSimpleArgBuilder(marshalType, elementVarEnum));491                }492 493                // Checking for IConvertible.494                if (typeof(IConvertible).IsAssignableFrom(elementType))495                {496                    return new ConvertibleArgBuilder();497                }498            }499            return GetSimpleArgBuilder(elementType, elementVarEnum);500        }501 502        // This helper can produce a builder for types that are directly supported by Variant.503        private static SimpleArgBuilder GetSimpleArgBuilder(Type elementType, VarEnum elementVarEnum)504        {505            SimpleArgBuilder argBuilder;506 507            switch (elementVarEnum)508            {509                case VarEnum.VT_BSTR:510                    argBuilder = new StringArgBuilder(elementType);511                    break;512                case VarEnum.VT_BOOL:513                    argBuilder = new BoolArgBuilder(elementType);514                    break;515                case VarEnum.VT_DATE:516                    argBuilder = new DateTimeArgBuilder(elementType);517                    break;518                case VarEnum.VT_CY:519                    argBuilder = new CurrencyArgBuilder(elementType);520                    break;521                case VarEnum.VT_DISPATCH:522                    argBuilder = new DispatchArgBuilder(elementType);523                    break;524                case VarEnum.VT_UNKNOWN:525                    argBuilder = new UnknownArgBuilder(elementType);526                    break;527                case VarEnum.VT_VARIANT:528                case VarEnum.VT_ARRAY:529                case VarEnum.VT_RECORD:530                    argBuilder = new VariantArgBuilder(elementType);531                    break;532                case VarEnum.VT_ERROR:533                    argBuilder = new ErrorArgBuilder(elementType);534                    break;535                default:536                    Type marshalType = GetManagedMarshalType(elementVarEnum);537                    if (elementType == marshalType)538                    {539                        argBuilder = new SimpleArgBuilder(elementType);540                    }541                    else542                    {543                        argBuilder = new ConvertArgBuilder(elementType, marshalType);544                    }545                    break;546            }547 548            return argBuilder;549        }550    }551}552