[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/tlecomte/ClearScript/master/ClearScript/Util/ObjectHelpers.cs [Back]  [Original]

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.ClearScript.Util.COM;
using Microsoft.Win32;
using TYPEFLAGS = System.Runtime.InteropServices.ComTypes.TYPEFLAGS;
using TYPEKIND = System.Runtime.InteropServices.ComTypes.TYPEKIND;

namespace Microsoft.ClearScript.Util
{
    internal static class ObjectHelpers
    {
        public static Type GetTypeOrTypeInfo(this object value)
        {
            if (!MiscHelpers.PlatformIsWindows())
            {
                return value.GetType();
            }

            var type = value.GetType();
            IDispatch dispatch = null;

            Type typeInfo = null;
            TYPEKIND typeInfoKind = 0;
            TYPEFLAGS typeInfoFlags = 0;

            if (type.IsUnknownCOMObject())
            {
                // This appears to be a generic COM object with no specific type information.
                // Attempt to acquire COM type information via IDispatch or IProvideClassInfo.

                dispatch = value as IDispatch;
                if (dispatch != null)
                {
                    var tempTypeInfo = dispatch.GetTypeInfo();
                    if (tempTypeInfo != null)
                    {
                        typeInfo = GetTypeForTypeInfo(tempTypeInfo);
                        typeInfoKind = tempTypeInfo.GetKind();
                        typeInfoFlags = tempTypeInfo.GetFlags();
                    }
                }

                if (typeInfo == null)
                {
                    if (value is IProvideClassInfo provideClassInfo)
                    {
                        if (HResult.Succeeded(provideClassInfo.GetClassInfo(out var tempTypeInfo)))
                        {
                            typeInfo = GetTypeForTypeInfo(tempTypeInfo);
                            typeInfoKind = tempTypeInfo.GetKind();
                            typeInfoFlags = tempTypeInfo.GetFlags();
                        }
                    }
                }
            }

            if (typeInfo != null)
            {
                // If the COM type is a dispatch-only interface, use it. Such interfaces typically
                // aren't exposed via QueryInterface(), so there's no way to validate them anyway.

                if ((dispatch != null) && (typeInfoKind == TYPEKIND.TKIND_DISPATCH) && typeInfoFlags.HasFlag(TYPEFLAGS.TYPEFLAG_FDISPATCHABLE) && !typeInfoFlags.HasFlag(TYPEFLAGS.TYPEFLAG_FDUAL))
                {
                    return typeInfo;
                }

                // COM type information acquired in this manner may not actually be valid for the
                // original object. In some cases the original object implements a base interface.

                if (typeInfo.IsInstanceOfType(value))
                {
                    return typeInfo;
                }

                foreach (var interfaceType in typeInfo.GetInterfaces())
                {
                    if (interfaceType.IsInstanceOfType(value))
                    {
                        return interfaceType;
                    }
                }
            }

            return type;
        }

        private static Type GetTypeForTypeInfo(ITypeInfo typeInfo)
        {
            // ReSharper disable EmptyGeneralCatchClause

            try
            {
                var typeLib = typeInfo.GetContainingTypeLib(out var index);

                var assembly = LoadPrimaryInteropAssembly(typeLib);
                if (assembly != null)
                {
                    var name = typeInfo.GetManagedName();
                    var guid = typeInfo.GetGuid();

                    var type = assembly.GetType(name, false, true);
                    if ((type != null) && (type.GUID == guid))
                    {
                        return type;
                    }

                    var types = assembly.GetAllTypes().ToArray();
                    if ((index >= 0) && (index < types.Length))
                    {
                        type = types[index];
                        if ((type.GUID == guid) && (type.FullName == name))
                        {
                            return type;
                        }
                    }

                    type = types.FirstOrDefault(testType => (testType.GUID == guid) && (testType.FullName.Equals(name, StringComparison.OrdinalIgnoreCase)));
                    if (type != null)
                    {
                        return type;
                    }
                }

                return typeInfo.GetManagedType();
            }
            catch (Exception)
            {
            }

            return null;

            // ReSharper restore EmptyGeneralCatchClause
        }

        private static Assembly LoadPrimaryInteropAssembly(ITypeLib typeLib)
        {
            if (typeLib == null)
            {
                return null;
            }

            // ReSharper disable EmptyGeneralCatchClause

            try
            {
                using (var attrScope = typeLib.CreateAttrScope())
                {
                    if (GetPrimaryInteropAssembly(attrScope.Value.guid, attrScope.Value.wMajorVerNum, attrScope.Value.wMinorVerNum, out var name, out var codeBase))
                    {
                        return Assembly.Load(new AssemblyName(name) { CodeBase = codeBase });
                    }
                }
            }
            catch (Exception)
            {
            }

            return null;

            // ReSharper restore EmptyGeneralCatchClause
        }

        private static bool GetPrimaryInteropAssembly(Guid libid, int major, int minor, out string name, out string codeBase)
        {
            name = null;
            codeBase = null;

            using (var containerKey = Registry.ClassesRoot.OpenSubKey("TypeLib", false))
            {
                if (containerKey != null)
                {
                    var typeLibName = "{" + libid.ToString().ToUpper(CultureInfo.InvariantCulture) + "}";
                    using (var typeLibKey = containerKey.OpenSubKey(typeLibName))
                    {
                        if (typeLibKey != null)
                        {
                            var versionName = major.ToString("x", CultureInfo.InvariantCulture) + "." + minor.ToString("x", CultureInfo.InvariantCulture);
                            using (var versionKey = typeLibKey.OpenSubKey(versionName, false))
                            {
                                if (versionKey != null)
                                {
                                    name = (string)versionKey.GetValue("PrimaryInteropAssemblyName");
                                    codeBase = (string)versionKey.GetValue("PrimaryInteropAssemblyCodeBase");
                                }
                            }
                        }
                    }
                }
            }

            return name != null;
        }

        public static string GetFriendlyName(this object value)
        {
            return value.GetFriendlyName(null);
        }

        public static string GetFriendlyName(this object value, Type type)
        {
            if (type == null)
            {
                if (value == null)
                {
                    return "[null]";
                }

                type = value.GetType();
            }

            if (type.IsArray && (value != null))
            {
                var array = (Array)value;
                var dimensions = Enumerable.Range(0, type.GetArrayRank());
                var lengths = string.Join(",", dimensions.Select(array.GetLength));
                return MiscHelpers.FormatInvariant("{0}[{1}]", type.GetElementType().GetFriendlyName(), lengths);
            }

            if (type.IsUnknownCOMObject())
            {
                if (value is IDispatch dispatch)
                {
                    var typeInfo = dispatch.GetTypeInfo();
                    if (typeInfo != null)
                    {
                        return typeInfo.GetName();
                    }
                }
            }

            return type.GetFriendlyName();
        }

        public static T DynamicCast(this object value)
        {
            // ReSharper disable RedundantCast

            // the cast to dynamic is not redundant; removing it breaks tests
            return (T)(dynamic)value;

            // ReSharper restore RedundantCast
        }

        public static object ToDynamicResult(this object result, ScriptEngine engine)
        {
            if (result is Nonexistent)
            {
                return Undefined.Value;
            }

            if ((result is HostTarget) || (result is IPropertyBag))
            {
                // Returning an instance of HostTarget (an internal type) isn't likely to be
                // useful. Wrapping it in a dynamic object makes sense in this context. Wrapping
                // a property bag allows it to participate in dynamic invocation chaining, which
                // may be useful when dealing with things like host type collections. HostItem
                // supports dynamic conversion, so the client can unwrap the object if necessary.

                return HostItem.Wrap(engine, result);
            }

            return result;
        }

        #region Nested type: IProvideClassInfo

        [ComImport]
        [Guid("b196b283-bab4-101a-b69c-00aa00341d07")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IProvideClassInfo
        {
            [PreserveSig]
            int GetClassInfo(
                [Out] [MarshalAs(UnmanagedType.Interface)] out ITypeInfo typeInfo
            );
        }

        #endregion
    }
}

Web Proxy Viewer  |  New URL  |  Original Page