xronos /Microsoft.Dynamic/VarEnumSelector.cs

Language C# Lines 417
MD5 Hash a6c472f8daa8170a055a306f99384baf Estimated Cost $6,700 (why?)
Repository https://bitbucket.org/stefanrusek/xronos View Raw File
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
/* ****************************************************************************
 *
 * 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;
#if CODEPLEX_40
using System.Linq.Expressions;
#else
using Microsoft.Linq.Expressions;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
#if !CODEPLEX_40
using Microsoft.Runtime.CompilerServices;
#endif

#if CODEPLEX_40
using System.Dynamic.Utils;
#else
using Microsoft.Scripting.Utils;
#endif

#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(bool);
            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) {
            // Look for an exact match with a COM primitive type

            foreach (KeyValuePair<VarEnum, Type> kvp in _ComToManagedPrimitiveTypes) {
                if (kvp.Value == argumentType) {
                    primitiveVarEnum = kvp.Key;
                    return true;
                }
            }

            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) {
                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;
        }

        /// <summary>
        /// Get the COM Variant type that argument should be marshaled as for a call to COM
        /// </summary>
        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:
                    argBuilder = new SimpleArgBuilder(elementType);
                    break;
            }

            return argBuilder;
        }
    }
}

#endif
Back to Top