MegaBites-AI/Windows-powershell
0372
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 