/* **************************************************************************** * * 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 /// /// 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 /// internal class VarEnumSelector { private readonly VariantBuilder[] _variantBuilders; private static readonly Dictionary _ComToManagedPrimitiveTypes = CreateComToManagedPrimitiveTypes(); private static readonly IList> _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; } } /// /// 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. /// 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 CreateComToManagedPrimitiveTypes() { Dictionary dict = new Dictionary(); #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 /// /// 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. /// private static IList> 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; } /// /// Get the (one representative type for each) primitive type families that the argument can be converted to /// private static List GetConversionsToComPrimitiveTypeFamilies(Type argumentType) { List compatibleComTypes = new List(); foreach (IList 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; } /// /// 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. /// private static void CheckForAmbiguousMatch(Type argumentType, List 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; } /// /// Is there a unique primitive type that has the best conversion for the argument /// private static bool TryGetPrimitiveComTypeViaConversion(Type argumentType, out VarEnum primitiveVarEnum) { // Look for a unique type family that the argument can be converted to. List 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_ 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) { argumentType = Enum.GetUnderlyingType(argumentType); return GetComType(ref argumentType); } // COM cannot express valuetype nulls so we will convert to underlying type // it will throw if there is no value if (TypeUtils.IsNullableType(argumentType)) { argumentType = TypeUtils.GetNonNullableType(argumentType); return GetComType(ref argumentType); } //generic types cannot be exposed to COM so they do not implement COM interfaces. if (argumentType.IsGenericType) { return VarEnum.VT_UNKNOWN; } VarEnum primitiveVarEnum; if (TryGetPrimitiveComType(argumentType, out primitiveVarEnum)) { return primitiveVarEnum; } // We could not find a way to marshal the type as a specific COM type return VT_DEFAULT; } /// /// Get the COM Variant type that argument should be marshaled as for a call to COM /// private VariantBuilder GetVariantBuilder(Type argumentType) { //argumentType is coming from MarshalType, null means the dynamic object holds //a null value and not byref if (argumentType == null) { return new VariantBuilder(VarEnum.VT_EMPTY, new NullArgBuilder()); } if (argumentType == typeof(DBNull)) { return new VariantBuilder(VarEnum.VT_NULL, new NullArgBuilder()); } ArgBuilder argBuilder; if (argumentType.IsByRef) { Type elementType = argumentType.GetElementType(); VarEnum elementVarEnum; if (elementType == typeof(object) || elementType == typeof(DBNull)) { //no meaningful value to pass ByRef. //perhaps the calee will replace it with something. //need to pass as a variant reference elementVarEnum = VarEnum.VT_VARIANT; } else { elementVarEnum = GetComType(ref elementType); } argBuilder = GetSimpleArgBuilder(elementType, elementVarEnum); return new VariantBuilder(elementVarEnum | VarEnum.VT_BYREF, argBuilder); } VarEnum varEnum = GetComType(ref argumentType); argBuilder = GetByValArgBuilder(argumentType, ref varEnum); return new VariantBuilder(varEnum, argBuilder); } // This helper is called when we are looking for a ByVal marhsalling // In a ByVal case we can take into account conversions or IConvertible if all other // attempts to find marshalling type failed private static ArgBuilder GetByValArgBuilder(Type elementType, ref VarEnum elementVarEnum) { // if VT indicates that marshalling type is unknown if (elementVarEnum == VT_DEFAULT) { //trying to find a conversion. VarEnum convertibleTo; if (TryGetPrimitiveComTypeViaConversion(elementType, out convertibleTo)) { elementVarEnum = convertibleTo; Type marshalType = GetManagedMarshalType(elementVarEnum); return new ConversionArgBuilder(elementType, GetSimpleArgBuilder(marshalType, elementVarEnum)); } //checking for IConvertible. if (typeof(IConvertible).IsAssignableFrom(elementType)) { return new ConvertibleArgBuilder(); } } return GetSimpleArgBuilder(elementType, elementVarEnum); } // This helper can produce a builder for types that are directly supported by Variant. private static SimpleArgBuilder GetSimpleArgBuilder(Type elementType, VarEnum elementVarEnum) { SimpleArgBuilder argBuilder; switch (elementVarEnum) { case VarEnum.VT_BSTR: argBuilder = new StringArgBuilder(elementType); break; case VarEnum.VT_BOOL: argBuilder = new BoolArgBuilder(elementType); break; case VarEnum.VT_DATE: argBuilder = new DateTimeArgBuilder(elementType); break; case VarEnum.VT_CY: argBuilder = new CurrencyArgBuilder(elementType); break; case VarEnum.VT_DISPATCH: argBuilder = new DispatchArgBuilder(elementType); break; case VarEnum.VT_UNKNOWN: argBuilder = new UnknownArgBuilder(elementType); break; case VarEnum.VT_VARIANT: case VarEnum.VT_ARRAY: case VarEnum.VT_RECORD: argBuilder = new VariantArgBuilder(elementType); break; case VarEnum.VT_ERROR: argBuilder = new ErrorArgBuilder(elementType); break; default: var marshalType = GetManagedMarshalType(elementVarEnum); if (elementType == marshalType) { argBuilder = new SimpleArgBuilder(elementType); } else { argBuilder = new ConvertArgBuilder(elementType, marshalType); } break; } return argBuilder; } } } #endif