PageRenderTime 276ms CodeModel.GetById 131ms app.highlight 17ms RepoModel.GetById 63ms app.codeStats 1ms

/Microsoft.Dynamic/VarEnumSelector.cs

https://bitbucket.org/stefanrusek/xronos
C# | 416 lines | 265 code | 63 blank | 88 comment | 57 complexity | a6c472f8daa8170a055a306f99384baf MD5 | raw file
  1/* ****************************************************************************
  2 *
  3 * Copyright (c) Microsoft Corporation. 
  4 *
  5 * This source code is subject to terms and conditions of the Microsoft Public License. A 
  6 * copy of the license can be found in the License.html file at the root of this distribution. If 
  7 * you cannot locate the  Microsoft Public License, please send an email to 
  8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
  9 * by the terms of the Microsoft Public License.
 10 *
 11 * You must not remove this notice, or any other, from this software.
 12 *
 13 *
 14 * ***************************************************************************/
 15using System; using Microsoft;
 16
 17
 18#if !SILVERLIGHT // ComObject
 19
 20using System.Collections.Generic;
 21using System.Diagnostics;
 22#if CODEPLEX_40
 23using System.Linq.Expressions;
 24#else
 25using Microsoft.Linq.Expressions;
 26#endif
 27using System.Reflection;
 28using System.Runtime.InteropServices;
 29using System.Runtime.CompilerServices;
 30#if !CODEPLEX_40
 31using Microsoft.Runtime.CompilerServices;
 32#endif
 33
 34#if CODEPLEX_40
 35using System.Dynamic.Utils;
 36#else
 37using Microsoft.Scripting.Utils;
 38#endif
 39
 40#if CODEPLEX_40
 41namespace System.Dynamic {
 42#else
 43namespace Microsoft.Scripting {
 44#endif
 45    /// <summary>
 46    /// 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
 47    /// to determine the VarEnum type we will marshal it as. We have the following options:
 48    /// 1.	Raise an exception. Languages with their own version of primitive types would not be able to call
 49    ///     COM methods using the language's types (for eg. strings in IronRuby are not System.String). An explicit
 50    ///     cast would be needed.
 51    /// 2.	We could marshal it as VT_DISPATCH. Then COM code will be able to access all the APIs in a late-bound manner,
 52    ///     but old COM components will probably malfunction if they expect a primitive type.
 53    /// 3.	We could guess which primitive type is the closest match. This will make COM components be as easily 
 54    ///     accessible as .NET methods.
 55    /// 4.	We could use the type library to check what the expected type is. However, the type library may not be available.
 56    /// 
 57    /// VarEnumSelector implements option # 3
 58    /// </summary>
 59    internal class VarEnumSelector {
 60        private readonly VariantBuilder[] _variantBuilders;
 61
 62        private static readonly Dictionary<VarEnum, Type> _ComToManagedPrimitiveTypes = CreateComToManagedPrimitiveTypes();
 63        private static readonly IList<IList<VarEnum>> _ComPrimitiveTypeFamilies = CreateComPrimitiveTypeFamilies();
 64
 65        internal VarEnumSelector(Type[] explicitArgTypes) {
 66            _variantBuilders = new VariantBuilder[explicitArgTypes.Length];
 67
 68            for (int i = 0; i < explicitArgTypes.Length; i++) {
 69                _variantBuilders[i] = GetVariantBuilder(explicitArgTypes[i]);
 70            }
 71        }
 72
 73        internal VariantBuilder[] VariantBuilders {
 74            get {
 75                return _variantBuilders;
 76            }
 77        }
 78
 79        /// <summary>
 80        /// Gets the managed type that an object needs to be coverted to in order for it to be able
 81        /// to be represented as a Variant.
 82        /// 
 83        /// In general, there is a many-to-many mapping between Type and VarEnum. However, this method
 84        /// returns a simple mapping that is needed for the current implementation. The reason for the 
 85        /// many-to-many relation is:
 86        /// 1. Int32 maps to VT_I4 as well as VT_ERROR, and Decimal maps to VT_DECIMAL and VT_CY. However,
 87        ///    this changes if you throw the wrapper types into the mix.
 88        /// 2. There is no Type to represent COM types. __ComObject is a private type, and Object is too
 89        ///    general.
 90        /// </summary>
 91        internal static Type GetManagedMarshalType(VarEnum varEnum) {
 92            Debug.Assert((varEnum & VarEnum.VT_BYREF) == 0);
 93
 94            if (varEnum == VarEnum.VT_CY) {
 95                return typeof(CurrencyWrapper);
 96            }
 97
 98            if (Variant.IsPrimitiveType(varEnum)) {
 99                return _ComToManagedPrimitiveTypes[varEnum];
100            }
101
102            switch (varEnum) {
103                case VarEnum.VT_EMPTY:
104                case VarEnum.VT_NULL:
105                case VarEnum.VT_UNKNOWN:
106                case VarEnum.VT_DISPATCH:
107                case VarEnum.VT_VARIANT:
108                    return typeof(Object);
109
110                case VarEnum.VT_ERROR:
111                    return typeof(ErrorWrapper);
112
113                default:
114                    throw Error.UnexpectedVarEnum(varEnum);
115            }
116        }
117
118        private static Dictionary<VarEnum, Type> CreateComToManagedPrimitiveTypes() {
119            Dictionary<VarEnum, Type> dict = new Dictionary<VarEnum, Type>();
120
121            #region Generated ComToManagedPrimitiveTypes
122
123            // *** BEGIN GENERATED CODE ***
124            // generated by function: gen_ComToManagedPrimitiveTypes from: generate_comdispatch.py
125
126            dict[VarEnum.VT_I1] = typeof(SByte);
127            dict[VarEnum.VT_I2] = typeof(Int16);
128            dict[VarEnum.VT_I4] = typeof(Int32);
129            dict[VarEnum.VT_I8] = typeof(Int64);
130            dict[VarEnum.VT_UI1] = typeof(Byte);
131            dict[VarEnum.VT_UI2] = typeof(UInt16);
132            dict[VarEnum.VT_UI4] = typeof(UInt32);
133            dict[VarEnum.VT_UI8] = typeof(UInt64);
134            dict[VarEnum.VT_INT] = typeof(IntPtr);
135            dict[VarEnum.VT_UINT] = typeof(UIntPtr);
136            dict[VarEnum.VT_BOOL] = typeof(bool);
137            dict[VarEnum.VT_R4] = typeof(Single);
138            dict[VarEnum.VT_R8] = typeof(Double);
139            dict[VarEnum.VT_DECIMAL] = typeof(Decimal);
140            dict[VarEnum.VT_DATE] = typeof(DateTime);
141            dict[VarEnum.VT_BSTR] = typeof(String);
142
143            // *** END GENERATED CODE ***
144
145            #endregion
146
147            dict[VarEnum.VT_CY] = typeof(CurrencyWrapper);
148            dict[VarEnum.VT_ERROR] = typeof(ErrorWrapper);
149
150            return dict;
151        }
152
153        #region Primitive COM types
154
155        /// <summary>
156        /// Creates a family of COM types such that within each family, there is a completely non-lossy
157        /// conversion from a type to an earlier type in the family.
158        /// </summary>
159        private static IList<IList<VarEnum>> CreateComPrimitiveTypeFamilies() {
160            VarEnum[][] typeFamilies = new VarEnum[][] {
161                new VarEnum[] { VarEnum.VT_I8, VarEnum.VT_I4, VarEnum.VT_I2, VarEnum.VT_I1 },
162                new VarEnum[] { VarEnum.VT_UI8, VarEnum.VT_UI4, VarEnum.VT_UI2, VarEnum.VT_UI1 },
163                new VarEnum[] { VarEnum.VT_INT },
164                new VarEnum[] { VarEnum.VT_UINT },
165                new VarEnum[] { VarEnum.VT_BOOL },
166                new VarEnum[] { VarEnum.VT_DATE },
167                new VarEnum[] { VarEnum.VT_R8, VarEnum.VT_R4 },
168                new VarEnum[] { VarEnum.VT_DECIMAL },
169                new VarEnum[] { VarEnum.VT_BSTR },
170
171                // wrappers
172                new VarEnum[] { VarEnum.VT_CY },
173                new VarEnum[] { VarEnum.VT_ERROR },
174            };
175
176            return typeFamilies;
177        }
178
179        /// <summary>
180        /// Get the (one representative type for each) primitive type families that the argument can be converted to
181        /// </summary>
182        private static List<VarEnum> GetConversionsToComPrimitiveTypeFamilies(Type argumentType) {
183            List<VarEnum> compatibleComTypes = new List<VarEnum>();
184
185            foreach (IList<VarEnum> typeFamily in _ComPrimitiveTypeFamilies) {
186                foreach (VarEnum candidateType in typeFamily) {
187                    Type candidateManagedType = _ComToManagedPrimitiveTypes[candidateType];
188                    if (TypeUtils.IsImplicitlyConvertible(argumentType, candidateManagedType, true)) {
189                        compatibleComTypes.Add(candidateType);
190                        // Move on to the next type family. We need atmost one type from each family
191                        break;
192                    }
193                }
194            }
195            return compatibleComTypes;
196        }
197
198        /// <summary>
199        /// If there is more than one type family that the argument can be converted to, we will throw a
200        /// AmbiguousMatchException instead of randomly picking a winner.
201        /// </summary>
202        private static void CheckForAmbiguousMatch(Type argumentType, List<VarEnum> compatibleComTypes) {
203            if (compatibleComTypes.Count <= 1) {
204                return;
205            }
206
207            String typeNames = "";
208            for (int i = 0; i < compatibleComTypes.Count; i++) {
209                string typeName = _ComToManagedPrimitiveTypes[compatibleComTypes[i]].Name;
210                if (i == (compatibleComTypes.Count - 1)) {
211                    typeNames += " and ";
212                } else if (i != 0) {
213                    typeNames += ", ";
214                }
215                typeNames += typeName;
216            }
217
218
219            throw Error.AmbiguousConversion(argumentType.Name, typeNames);
220        }
221
222        private static bool TryGetPrimitiveComType(Type argumentType, out VarEnum primitiveVarEnum) {
223            // Look for an exact match with a COM primitive type
224
225            foreach (KeyValuePair<VarEnum, Type> kvp in _ComToManagedPrimitiveTypes) {
226                if (kvp.Value == argumentType) {
227                    primitiveVarEnum = kvp.Key;
228                    return true;
229                }
230            }
231
232            primitiveVarEnum = VarEnum.VT_VOID; // error
233            return false;
234        }
235
236        /// <summary>
237        /// Is there a unique primitive type that has the best conversion for the argument
238        /// </summary>
239        private static bool TryGetPrimitiveComTypeViaConversion(Type argumentType, out VarEnum primitiveVarEnum) {
240            // Look for a unique type family that the argument can be converted to.
241            List<VarEnum> compatibleComTypes = GetConversionsToComPrimitiveTypeFamilies(argumentType);
242            CheckForAmbiguousMatch(argumentType, compatibleComTypes);
243            if (compatibleComTypes.Count == 1) {
244                primitiveVarEnum = compatibleComTypes[0];
245                return true;
246            }
247
248            primitiveVarEnum = VarEnum.VT_VOID; // error
249            return false;
250        }
251
252        #endregion
253
254        // Type.InvokeMember tries to marshal objects as VT_DISPATCH, and falls back to VT_UNKNOWN
255        // VT_RECORD here just indicates that we have user defined type.
256        // We will try VT_DISPATCH and then call GetNativeVariantForObject.
257        const VarEnum VT_DEFAULT = VarEnum.VT_RECORD;
258
259        private VarEnum GetComType(ref Type argumentType) {
260            if (argumentType == typeof(Missing)) {
261                //actual variant type will be VT_ERROR | E_PARAMNOTFOUND 
262                return VarEnum.VT_RECORD;
263            }
264
265            if (argumentType.IsArray) {
266                //actual variant type will be VT_ARRAY | VT_<ELEMENT_TYPE>
267                return VarEnum.VT_ARRAY;
268            }
269
270            if (argumentType == typeof(UnknownWrapper)) {
271                return VarEnum.VT_UNKNOWN;
272            } else if (argumentType == typeof(DispatchWrapper)) {
273                return VarEnum.VT_DISPATCH;
274            } else if (argumentType == typeof(VariantWrapper)) {
275                return VarEnum.VT_VARIANT;
276            } else if (argumentType == typeof(BStrWrapper)) {
277                return VarEnum.VT_BSTR;
278            } else if (argumentType == typeof(ErrorWrapper)) {
279                return VarEnum.VT_ERROR;
280            } else if (argumentType == typeof(CurrencyWrapper)) {
281                return VarEnum.VT_CY;
282            }
283
284            // Many languages require an explicit cast for an enum to be used as the underlying type.
285            // However, we want to allow this conversion for COM without requiring an explicit cast
286            // so that enums from interop assemblies can be used as arguments. 
287            if (argumentType.IsEnum) {
288                argumentType = Enum.GetUnderlyingType(argumentType);
289                return GetComType(ref argumentType);
290            }
291
292            // COM cannot express valuetype nulls so we will convert to underlying type
293            // it will throw if there is no value
294            if (TypeUtils.IsNullableType(argumentType)) {
295                argumentType = TypeUtils.GetNonNullableType(argumentType);
296                return GetComType(ref argumentType);
297            }
298
299            //generic types cannot be exposed to COM so they do not implement COM interfaces.
300            if (argumentType.IsGenericType) {
301                return VarEnum.VT_UNKNOWN;
302            }
303
304            VarEnum primitiveVarEnum;
305            if (TryGetPrimitiveComType(argumentType, out primitiveVarEnum)) {
306                return primitiveVarEnum;
307            }
308
309            // We could not find a way to marshal the type as a specific COM type
310            return VT_DEFAULT;
311        }
312
313        /// <summary>
314        /// Get the COM Variant type that argument should be marshaled as for a call to COM
315        /// </summary>
316        private VariantBuilder GetVariantBuilder(Type argumentType) {
317            //argumentType is coming from MarshalType, null means the dynamic object holds
318            //a null value and not byref
319            if (argumentType == null) {
320                return new VariantBuilder(VarEnum.VT_EMPTY, new NullArgBuilder());
321            }
322
323            if (argumentType == typeof(DBNull)) {
324                return new VariantBuilder(VarEnum.VT_NULL, new NullArgBuilder());
325            }
326
327            ArgBuilder argBuilder;
328
329            if (argumentType.IsByRef) {
330                Type elementType = argumentType.GetElementType();
331
332                VarEnum elementVarEnum;
333                if (elementType == typeof(object) || elementType == typeof(DBNull)) {
334                    //no meaningful value to pass ByRef. 
335                    //perhaps the calee will replace it with something.
336                    //need to pass as a variant reference
337                    elementVarEnum = VarEnum.VT_VARIANT;
338                } else {
339                    elementVarEnum = GetComType(ref elementType);
340                }
341
342                argBuilder = GetSimpleArgBuilder(elementType, elementVarEnum);
343                return new VariantBuilder(elementVarEnum | VarEnum.VT_BYREF, argBuilder);
344            }
345
346            VarEnum varEnum = GetComType(ref argumentType);
347            argBuilder = GetByValArgBuilder(argumentType, ref varEnum);
348
349            return new VariantBuilder(varEnum, argBuilder);
350        }
351
352
353        // This helper is called when we are looking for a ByVal marhsalling
354        // In a ByVal case we can take into account conversions or IConvertible if all other 
355        // attempts to find marshalling type failed 
356        private static ArgBuilder GetByValArgBuilder(Type elementType, ref VarEnum elementVarEnum) {
357            // if VT indicates that marshalling type is unknown
358            if (elementVarEnum == VT_DEFAULT) {
359                //trying to find a conversion.
360                VarEnum convertibleTo;
361                if (TryGetPrimitiveComTypeViaConversion(elementType, out convertibleTo)) {
362                    elementVarEnum = convertibleTo;
363                    Type marshalType = GetManagedMarshalType(elementVarEnum);
364                    return new ConversionArgBuilder(elementType, GetSimpleArgBuilder(marshalType, elementVarEnum));
365                }
366
367                //checking for IConvertible.
368                if (typeof(IConvertible).IsAssignableFrom(elementType)) {
369                    return new ConvertibleArgBuilder();
370                }
371            }
372            return GetSimpleArgBuilder(elementType, elementVarEnum);
373        }
374
375        // This helper can produce a builder for types that are directly supported by Variant.
376        private static SimpleArgBuilder GetSimpleArgBuilder(Type elementType, VarEnum elementVarEnum) {
377            SimpleArgBuilder argBuilder;
378
379            switch (elementVarEnum) {
380                case VarEnum.VT_BSTR:
381                    argBuilder = new StringArgBuilder(elementType);
382                    break;
383                case VarEnum.VT_BOOL:
384                    argBuilder = new BoolArgBuilder(elementType);
385                    break;
386                case VarEnum.VT_DATE:
387                    argBuilder = new DateTimeArgBuilder(elementType);
388                    break;
389                case VarEnum.VT_CY:
390                    argBuilder = new CurrencyArgBuilder(elementType);
391                    break;
392                case VarEnum.VT_DISPATCH:
393                    argBuilder = new DispatchArgBuilder(elementType);
394                    break;
395                case VarEnum.VT_UNKNOWN:
396                    argBuilder = new UnknownArgBuilder(elementType);
397                    break;
398                case VarEnum.VT_VARIANT:
399                case VarEnum.VT_ARRAY:
400                case VarEnum.VT_RECORD:
401                    argBuilder = new VariantArgBuilder(elementType);
402                    break;
403                case VarEnum.VT_ERROR:
404                    argBuilder = new ErrorArgBuilder(elementType);
405                    break;
406                default:
407                    argBuilder = new SimpleArgBuilder(elementType);
408                    break;
409            }
410
411            return argBuilder;
412        }
413    }
414}
415
416#endif