context: /* ****************************************************************************
   *
   * Copyright (c) Microsoft Corporation. 
   *
   * This source code is subject to terms and conditions of the Microsoft Public License. A 
   * copy of the license can be found in the License.html file at the root of this distribution. If 
   * you cannot locate the  Microsoft Public License, please send an email to 
   * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
   * by the terms of the Microsoft Public License.
   *
   * You must not remove this notice, or any other, from this software.
   *
   *
   * ***************************************************************************/
  using System; using Microsoft;
  
  
  #if !SILVERLIGHT // ComObject
  
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Reflection;
  using System.Runtime.InteropServices;
  
  #if CODEPLEX_40
  namespace System.Dynamic {
  #else
  namespace Microsoft.Scripting {
  #endif
      /// <summary>
      /// 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 need
      /// to determine the VarEnum type we will marshal it as. We have the following options:
      /// 1.	Raise an exception. Languages with their own version of primitive types would not be able to call
      ///     COM methods using the language's types (for eg. strings in IronRuby are not System.String). An explicit
      ///     cast would be needed.
      /// 2.	We could marshal it as VT_DISPATCH. Then COM code will be able to access all the APIs in a late-bound manner,
      ///     but old COM components will probably malfunction if they expect a primitive type.
      /// 3.	We could guess which primitive type is the closest match. This will make COM components be as easily 
      ///     accessible as .NET methods.
      /// 4.	We could use the type library to check what the expected type is. However, the type library may not be available.
      /// 
      /// VarEnumSelector implements option # 3
      /// </summary>
      internal class VarEnumSelector {
          private readonly VariantBuilder[] _variantBuilders;
  
          private static readonly Dictionary<VarEnum, Type> _ComToManagedPrimitiveTypes = CreateComToManagedPrimitiveTypes();
          private static readonly IList<IList<VarEnum>> _ComPrimitiveTypeFamilies = CreateComPrimitiveTypeFamilies();
  
          internal VarEnumSelector(Type[] explicitArgTypes) {
              _variantBuilders = new VariantBuilder[explicitArgTypes.Length];
  
              for (int i = 0; i < explicitArgTypes.Length; i++) {
                  _variantBuilders[i] = GetVariantBuilder(explicitArgTypes[i]);
              }
          }
  
          internal VariantBuilder[] VariantBuilders {
              get {
                  return _variantBuilders;
              }
          }
  
          /// <summary>
          /// Gets the managed type that an object needs to be coverted to in order for it to be able
          /// to be represented as a Variant.
          /// 
          /// In general, there is a many-to-many mapping between Type and VarEnum. However, this method
          /// returns a simple mapping that is needed for the current implementation. The reason for the 
          /// many-to-many relation is:
          /// 1. Int32 maps to VT_I4 as well as VT_ERROR, and Decimal maps to VT_DECIMAL and VT_CY. However,
          ///    this changes if you throw the wrapper types into the mix.
          /// 2. There is no Type to represent COM types. __ComObject is a private type, and Object is too
          ///    general.
          /// </summary>
          internal static Type GetManagedMarshalType(VarEnum varEnum) {
              Debug.Assert((varEnum & VarEnum.VT_BYREF) == 0);
  
              if (varEnum == VarEnum.VT_CY) {
                  return typeof(CurrencyWrapper);
              }
  
              if (Variant.IsPrimitiveType(varEnum)) {
                  return _ComToManagedPrimitiveTypes[varEnum];
              }
  
              switch (varEnum) {
                  case VarEnum.VT_EMPTY:
                  case VarEnum.VT_NULL:
                  case VarEnum.VT_UNKNOWN:
                  case VarEnum.VT_DISPATCH:
                  case VarEnum.VT_VARIANT:
                      return typeof(Object);
  
                  case VarEnum.VT_ERROR:
                      return typeof(ErrorWrapper);
  
                  default:
                      throw Error.UnexpectedVarEnum(varEnum);
              }
          }
  
          private static Dictionary<VarEnum, Type> CreateComToManagedPrimitiveTypes() {
              Dictionary<VarEnum, Type> dict = new Dictionary<VarEnum, Type>();
  
              #region Generated ComToManagedPrimitiveTypes
  
              // *** BEGIN GENERATED CODE ***
              // generated by function: gen_ComToManagedPrimitiveTypes from: generate_comdispatch.py
  
              dict[VarEnum.VT_I1] = typeof(SByte);
              dict[VarEnum.VT_I2] = typeof(Int16);
              dict[VarEnum.VT_I4] = typeof(Int32);
              dict[VarEnum.VT_I8] = typeof(Int64);
              dict[VarEnum.VT_UI1] = typeof(Byte);
              dict[VarEnum.VT_UI2] = typeof(UInt16);
              dict[VarEnum.VT_UI4] = typeof(UInt32);
              dict[VarEnum.VT_UI8] = typeof(UInt64);
              dict[VarEnum.VT_INT] = typeof(IntPtr);
              dict[VarEnum.VT_UINT] = typeof(UIntPtr);
              dict[VarEnum.VT_BOOL] = typeof(Boolean);
              dict[VarEnum.VT_R4] = typeof(Single);
              dict[VarEnum.VT_R8] = typeof(Double);
              dict[VarEnum.VT_DECIMAL] = typeof(Decimal);
              dict[VarEnum.VT_DATE] = typeof(DateTime);
              dict[VarEnum.VT_BSTR] = typeof(String);
  
              // *** END GENERATED CODE ***
  
              #endregion
  
              dict[VarEnum.VT_CY] = typeof(CurrencyWrapper);
              dict[VarEnum.VT_ERROR] = typeof(ErrorWrapper);
  
              return dict;
          }
  
          #region Primitive COM types
  
          /// <summary>
          /// Creates a family of COM types such that within each family, there is a completely non-lossy
          /// conversion from a type to an earlier type in the family.
          /// </summary>
          private static IList<IList<VarEnum>> CreateComPrimitiveTypeFamilies() {
              VarEnum[][] typeFamilies = new VarEnum[][] {
                  new VarEnum[] { VarEnum.VT_I8, VarEnum.VT_I4, VarEnum.VT_I2, VarEnum.VT_I1 },
                  new VarEnum[] { VarEnum.VT_UI8, VarEnum.VT_UI4, VarEnum.VT_UI2, VarEnum.VT_UI1 },
                  new VarEnum[] { VarEnum.VT_INT },
                  new VarEnum[] { VarEnum.VT_UINT },
                  new VarEnum[] { VarEnum.VT_BOOL },
                  new VarEnum[] { VarEnum.VT_DATE },
                  new VarEnum[] { VarEnum.VT_R8, VarEnum.VT_R4 },
                  new VarEnum[] { VarEnum.VT_DECIMAL },
                  new VarEnum[] { VarEnum.VT_BSTR },
  
                  // wrappers
                  new VarEnum[] { VarEnum.VT_CY },
                  new VarEnum[] { VarEnum.VT_ERROR },
              };
  
              return typeFamilies;
          }
  
          /// <summary>
          /// Get the (one representative type for each) primitive type families that the argument can be converted to
          /// </summary>
          private static List<VarEnum> GetConversionsToComPrimitiveTypeFamilies(Type argumentType) {
              List<VarEnum> compatibleComTypes = new List<VarEnum>();
  
              foreach (IList<VarEnum> typeFamily in _ComPrimitiveTypeFamilies) {
                  foreach (VarEnum candidateType in typeFamily) {
                      Type candidateManagedType = _ComToManagedPrimitiveTypes[candidateType];
                      if (TypeUtils.IsImplicitlyConvertible(argumentType, candidateManagedType, true)) {
                          compatibleComTypes.Add(candidateType);
                          // Move on to the next type family. We need atmost one type from each family
                          break;
                      }
                  }
              }
              return compatibleComTypes;
          }
  
          /// <summary>
          /// If there is more than one type family that the argument can be converted to, we will throw a
          /// AmbiguousMatchException instead of randomly picking a winner.
          /// </summary>
          private static void CheckForAmbiguousMatch(Type argumentType, List<VarEnum> compatibleComTypes) {
              if (compatibleComTypes.Count <= 1) {
                  return;
              }
  
              String typeNames = "";
              for (int i = 0; i < compatibleComTypes.Count; i++) {
                  string typeName = _ComToManagedPrimitiveTypes[compatibleComTypes[i]].Name;
                  if (i == (compatibleComTypes.Count - 1)) {
                      typeNames += " and ";
                  } else if (i != 0) {
                      typeNames += ", ";
                  }
                  typeNames += typeName;
              }
  
  
              throw Error.AmbiguousConversion(argumentType.Name, typeNames);
          }
  
          private static bool TryGetPrimitiveComType(Type argumentType, out VarEnum primitiveVarEnum) {
  
              #region Generated Managed To COM Primitive Type Map
  
              // *** BEGIN GENERATED CODE ***
              // generated by function: gen_ManagedToComPrimitiveTypes from: generate_comdispatch.py
  
              switch (Type.GetTypeCode(argumentType)) {
                  case TypeCode.Boolean:
                      primitiveVarEnum = VarEnum.VT_BOOL;
                      return true;
                  case TypeCode.Char:
                      primitiveVarEnum = VarEnum.VT_UI2;
                      return true;
                  case TypeCode.SByte:
                      primitiveVarEnum = VarEnum.VT_I1;
                      return true;
                  case TypeCode.Byte:
                      primitiveVarEnum = VarEnum.VT_UI1;
                      return true;
                  case TypeCode.Int16:
                      primitiveVarEnum = VarEnum.VT_I2;
                      return true;
                  case TypeCode.UInt16:
                      primitiveVarEnum = VarEnum.VT_UI2;
                      return true;
                  case TypeCode.Int32:
                      primitiveVarEnum = VarEnum.VT_I4;
                      return true;
                  case TypeCode.UInt32:
                      primitiveVarEnum = VarEnum.VT_UI4;
                      return true;
                  case TypeCode.Int64:
                      primitiveVarEnum = VarEnum.VT_I8;
                      return true;
                  case TypeCode.UInt64:
                      primitiveVarEnum = VarEnum.VT_UI8;
                      return true;
                  case TypeCode.Single:
                      primitiveVarEnum = VarEnum.VT_R4;
                      return true;
                  case TypeCode.Double:
                      primitiveVarEnum = VarEnum.VT_R8;
                      return true;
                  case TypeCode.Decimal:
                      primitiveVarEnum = VarEnum.VT_DECIMAL;
                      return true;
                  case TypeCode.DateTime:
                      primitiveVarEnum = VarEnum.VT_DATE;
                      return true;
                  case TypeCode.String:
                      primitiveVarEnum = VarEnum.VT_BSTR;
                      return true;
              }
  
              if (argumentType == typeof(CurrencyWrapper)) {
                  primitiveVarEnum = VarEnum.VT_CY;
                  return true;
              }
  
              if (argumentType == typeof(ErrorWrapper)) {
                  primitiveVarEnum = VarEnum.VT_ERROR;
                  return true;
              }
  
              if (argumentType == typeof(IntPtr)) {
                  primitiveVarEnum = VarEnum.VT_INT;
                  return true;
              }
  
              if (argumentType == typeof(UIntPtr)) {
                  primitiveVarEnum = VarEnum.VT_UINT;
                  return true;
              }
  
              // *** END GENERATED CODE ***
  
              #endregion
  
              primitiveVarEnum = VarEnum.VT_VOID; // error
              return false;
          }
  
          /// <summary>
          /// Is there a unique primitive type that has the best conversion for the argument
          /// </summary>
          private static bool TryGetPrimitiveComTypeViaConversion(Type argumentType, out VarEnum primitiveVarEnum) {
              // Look for a unique type family that the argument can be converted to.
              List<VarEnum> compatibleComTypes = GetConversionsToComPrimitiveTypeFamilies(argumentType);
              CheckForAmbiguousMatch(argumentType, compatibleComTypes);
              if (compatibleComTypes.Count == 1) {
                  primitiveVarEnum = compatibleComTypes[0];
                  return true;
              }
  
              primitiveVarEnum = VarEnum.VT_VOID; // error
              return false;
          }
  
          #endregion
  
          // Type.InvokeMember tries to marshal objects as VT_DISPATCH, and falls back to VT_UNKNOWN
          // VT_RECORD here just indicates that we have user defined type.
          // We will try VT_DISPATCH and then call GetNativeVariantForObject.
          const VarEnum VT_DEFAULT = VarEnum.VT_RECORD;
  
          private VarEnum GetComType(ref Type argumentType) {
              if (argumentType == typeof(Missing)) {
                  //actual variant type will be VT_ERROR | E_PARAMNOTFOUND 
                  return VarEnum.VT_RECORD;
              }
  
              if (argumentType.IsArray) {
                  //actual variant type will be VT_ARRAY | VT_<ELEMENT_TYPE>
                  return VarEnum.VT_ARRAY;
              }
  
              if (argumentType == typeof(UnknownWrapper)) {
                  return VarEnum.VT_UNKNOWN;
              } else if (argumentType == typeof(DispatchWrapper)) {
                  return VarEnum.VT_DISPATCH;
              } else if (argumentType == typeof(VariantWrapper)) {
                  return VarEnum.VT_VARIANT;
              } else if (argumentType == typeof(BStrWrapper)) {
                  return VarEnum.VT_BSTR;
              } else if (argumentType == typeof(ErrorWrapper)) {
                  return VarEnum.VT_ERROR;
              } else if (argumentType == typeof(CurrencyWrapper)) {
                  return VarEnum.VT_CY;
              }
  
              // Many languages require an explicit cast for an enum to be used as the underlying type.
              // However, we want to allow this conversion for COM without requiring an explicit cast
              // so that enums from interop assemblies can be used as arguments. 
              if (argumentType.IsEnum) {
  
gt:                 argumentType = Enum.GetUnderlyingType(argumentType);