[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/phxnsharp/ClearScript/master/ClearScript/HostItem.NetCore.cs [Back]  [Original]

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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Expando;
using Microsoft.ClearScript.Util;
using Microsoft.ClearScript.Util.COM;

namespace Microsoft.ClearScript
{
    internal partial class HostItem
    {
        #region initialization

        private static HostItem Create(ScriptEngine engine, HostTarget target, HostItemFlags flags)
        {
            return TargetSupportsExpandoMembers(target, flags) ? new DispatchExHostItem(engine, target, flags) : new HostItem(engine, target, flags);
        }

        #endregion

        #region Nested type: DispatchExHostItem

        private sealed class DispatchExHostItem : ExpandoHostItem
        {
            #region data

            private static readonly Dictionary patchMap = new Dictionary();
            private readonly List expandoMembers = new List();

            #endregion

            #region IDispatch[Ex] delegates

            private delegate int RawGetIDsOfNames(
                [In] IntPtr pThis,
                [In] ref Guid iid,
                [In] [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)] string[] names,
                [In] uint count,
                [In] int lcid,
                [Out] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] int[] dispids
            );

            private delegate int RawGetDispID(
                [In] IntPtr pThis,
                [In] [MarshalAs(UnmanagedType.BStr)] string name,
                [In] DispatchNameFlags flags,
                [Out] out int dispid
            );

            private delegate int RawDeleteMemberByName(
                [In] IntPtr pThis,
                [In] [MarshalAs(UnmanagedType.BStr)] string name,
                [In] DispatchNameFlags flags
            );

            private delegate int RawDeleteMemberByDispID(
                [In] IntPtr pThis,
                [In] int dispid
            );

            private delegate int RawGetMemberProperties(
                [In] IntPtr pThis,
                [In] int dispid,
                [In] DispatchPropFlags fetchFlags,
                [Out] out DispatchPropFlags flags
            );

            private delegate int RawGetMemberName(
                [In] IntPtr pThis,
                [In] int dispid,
                [Out] [MarshalAs(UnmanagedType.BStr)] out string name
            );

            private delegate int RawGetNextDispID(
                [In] IntPtr pThis,
                [In] DispatchEnumFlags flags,
                [In] int dispidCurrent,
                [Out] out int dispidNext
            );

            #endregion

            #region constructors

            public DispatchExHostItem(ScriptEngine engine, HostTarget target, HostItemFlags flags)
                : base(engine, target, flags)
            {
                EnsurePatched();
            }

            #endregion

            #region internal members

            private IEnumerable ReflectMembers
            {
                get
                {
                    var members = ThisReflect.GetMembers(GetCommonBindFlags());
                    if (members.Length > 0)
                    {
                        using (var unknownScope = Scope.Create(() => Marshal.GetIUnknownForObject(this), pUnknown => Marshal.Release(pUnknown)))
                        {
                            var pUnknown = unknownScope.Value;
                            using (var dispatchScope = Scope.Create(() => UnknownHelpers.QueryInterface(pUnknown), pDispatch => Marshal.Release(pDispatch)))
                            {
                                var pDispatch = dispatchScope.Value;
                                var getIDsOfNames = VTableHelpers.GetMethodDelegate(pDispatch, 5);

                                var iid = Guid.Empty;
                                var names = new string[1];
                                var dispids = new int[1];

                                for (var index = 0; index < members.Length; index++)
                                {
                                    names[0] = members[index].Name;
                                    if (HResult.Succeeded(getIDsOfNames(pDispatch, ref iid, names, 1, 0, dispids)))
                                    {
                                        yield return new Member { Name = names[0], DispID = dispids[0], DispIDName = MiscHelpers.GetDispIDName(dispids[0]) };
                                    }
                                }
                            }
                        }
                    }
                }
            }

            private IExpando ThisExpando
            {
                get { return this; }
            }

            private void EnsurePatched()
            {
                using (var unknownScope = Scope.Create(() => Marshal.GetIUnknownForObject(this), pUnknown => Marshal.Release(pUnknown)))
                {
                    var pUnknown = unknownScope.Value;
                    using (var dispatchExScope = Scope.Create(() => UnknownHelpers.QueryInterface(pUnknown), pDispatchEx => Marshal.Release(pDispatchEx)))
                    {
                        lock (VTablePatcher.PatchLock)
                        {
                            var pDispatchEx = dispatchExScope.Value;
                            var pVTable = Marshal.ReadIntPtr(pDispatchEx);

                            if (!patchMap.ContainsKey(pVTable))
                            {
                                var entry = new PatchEntry();

                                var origGetDispID = VTableHelpers.GetMethodDelegate(pDispatchEx, 7);
                                entry.AddDelegate(VTableHelpers.SetMethodDelegate(pDispatchEx, 7, new RawGetDispID((IntPtr pThis, string name, DispatchNameFlags nameFlags, out int dispid) =>
                                {
                                    try
                                    {
                                        var item = Marshal.GetObjectForIUnknown(pThis) as DispatchExHostItem;
                                        return (item != null) ? item.GetDispID(name, nameFlags, out dispid) : origGetDispID(pThis, name, nameFlags, out dispid);
                                    }
                                    catch (Exception exception)
                                    {
                                        dispid = SpecialDispIDs.Unknown;
                                        return exception.HResult;
                                    }
                                })));

                                var origDeleteMemberByName = VTableHelpers.GetMethodDelegate(pDispatchEx, 9);
                                entry.AddDelegate(VTableHelpers.SetMethodDelegate(pDispatchEx, 9, new RawDeleteMemberByName((pThis, name, nameFlags) =>
                                {
                                    try
                                    {
                                        var item = Marshal.GetObjectForIUnknown(pThis) as DispatchExHostItem;
                                        return (item != null) ? item.DeleteMemberByName(name, nameFlags) : origDeleteMemberByName(pThis, name, nameFlags);
                                    }
                                    catch (Exception exception)
                                    {
                                        return exception.HResult;
                                    }
                                })));

                                var origDeleteMemberByDispID = VTableHelpers.GetMethodDelegate(pDispatchEx, 10);
                                entry.AddDelegate(VTableHelpers.SetMethodDelegate(pDispatchEx, 10, new RawDeleteMemberByDispID((pThis, dispid) =>
                                {
                                    try
                                    {
                                        var item = Marshal.GetObjectForIUnknown(pThis) as DispatchExHostItem;
                                        return (item != null) ? item.DeleteMemberByDispID(dispid) : origDeleteMemberByDispID(pThis, dispid);
                                    }
                                    catch (Exception exception)
                                    {
                                        return exception.HResult;
                                    }
                                })));

                                var origGetMemberProperties = VTableHelpers.GetMethodDelegate(pDispatchEx, 11);
                                entry.AddDelegate(VTableHelpers.SetMethodDelegate(pDispatchEx, 11, new RawGetMemberProperties((IntPtr pThis, int dispid, DispatchPropFlags fetchFlags, out DispatchPropFlags propFlags) =>
                                {
                                    try
                                    {
                                        var item = Marshal.GetObjectForIUnknown(pThis) as DispatchExHostItem;
                                        if (item == null)
                                        {
                                            return origGetMemberProperties(pThis, dispid, fetchFlags, out propFlags);
                                        }

                                        var result = item.GetMemberProperties(dispid, fetchFlags, out propFlags);
                                        if (result == HResult.DISP_E_MEMBERNOTFOUND)
                                        {
                                            return origGetMemberProperties(pThis, dispid, fetchFlags, out propFlags);
                                        }

                                        return result;
                                    }
                                    catch (Exception exception)
                                    {
                                        propFlags = 0;
                                        return exception.HResult;
                                    }
                                })));

                                var origGetMemberName = VTableHelpers.GetMethodDelegate(pDispatchEx, 12);
                                entry.AddDelegate(VTableHelpers.SetMethodDelegate(pDispatchEx, 12, new RawGetMemberName((IntPtr pThis, int dispid, out string name) =>
                                {
                                    try
                                    {
                                        var item = Marshal.GetObjectForIUnknown(pThis) as DispatchExHostItem;
                                        return (item != null) ? item.GetMemberName(dispid, out name) : origGetMemberName(pThis, dispid, out name);
                                    }
                                    catch (Exception exception)
                                    {
                                        name = null;
                                        return exception.HResult;
                                    }
                                })));

                                var origGetNextDispID = VTableHelpers.GetMethodDelegate(pDispatchEx, 13);
                                entry.AddDelegate(VTableHelpers.SetMethodDelegate(pDispatchEx, 13, new RawGetNextDispID((IntPtr pThis, DispatchEnumFlags enumFlags, int dispidCurrent, out int dispidNext) =>
                                {
                                    try
                                    {
                                        var item = Marshal.GetObjectForIUnknown(pThis) as DispatchExHostItem;
                                        return (item != null) ? item.GetNextDispID(dispidCurrent, out dispidNext) : origGetNextDispID(pThis, enumFlags, dispidCurrent, out dispidNext);
                                    }
                                    catch (Exception exception)
                                    {
                                        dispidNext = SpecialDispIDs.Unknown;
                                        return exception.HResult;
                                    }
                                })));

                                patchMap.Add(pVTable, entry);
                                Debug.Assert(patchMap.Count < 16);
                            }
                        }
                    }
                }
            }

            #endregion

            #region HostItem overrides

            protected override string AdjustInvokeName(string name)
            {
                var member = expandoMembers.FirstOrDefault(expandoMember => name == expandoMember.DispIDName);
                if (!member.IsDefault)
                {
                    return member.Name;
                }

                return name;
            }

            protected override void AddExpandoMemberName(string name)
            {
                var dispid = 1;

                var dispids = ReflectMembers.Concat(expandoMembers).Select(member => member.DispID).ToArray();
                for (; dispids.Contains(dispid); dispid++)
                {
                    if (dispid >= int.MaxValue)
                    {
                        throw new NotSupportedException("The object cannot support additional dynamic members");
                    }
                }

                base.AddExpandoMemberName(name);
                expandoMembers.Add(new Member { Name = name, DispID = dispid, DispIDName = MiscHelpers.GetDispIDName(dispid) });
            }

            protected override void RemoveExpandoMemberName(string name)
            {
                base.RemoveExpandoMemberName(name);

                var index = expandoMembers.FindIndex(member => member.Name == name);
                if (index >= 0)
                {
                    expandoMembers.RemoveAt(index);
                }
            }

            #endregion

            #region IDispatchEx patches

            private int GetDispID(string name, DispatchNameFlags nameFlags, out int dispid)
            {
                var nameComparison = nameFlags.HasFlag(DispatchNameFlags.CaseInsensitive) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;

                var member = ReflectMembers.Concat(expandoMembers).FirstOrDefault(testMember => string.Equals(testMember.Name, name, nameComparison));
                if (!member.IsDefault)
                {
                    dispid = member.DispID;
                    return HResult.S_OK;
                }

                if (nameFlags.HasFlag(DispatchNameFlags.Ensure))
                {
                    ThisExpando.AddProperty(name);
                    return GetDispID(name, nameFlags & ~DispatchNameFlags.Ensure, out dispid);
                }

                dispid = SpecialDispIDs.Unknown;
                return HResult.DISP_E_UNKNOWNNAME;
            }

            private int DeleteMemberByName(string name, DispatchNameFlags nameFlags)
            {
                var nameComparison = nameFlags.HasFlag(DispatchNameFlags.CaseInsensitive) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;

                var member = ReflectMembers.Concat(expandoMembers).FirstOrDefault(testMember => string.Equals(testMember.Name, name, nameComparison));
                if (!member.IsDefault && RemoveMember(member.Name))
                {
                    return HResult.S_OK;
                }

                // IDispatchEx specifies S_FALSE, but DISP_E_UNKNOWNNAME provides parity with .NET Framework CCWs
                return HResult.DISP_E_UNKNOWNNAME;
            }

            private int DeleteMemberByDispID(int dispid)
            {
                var member = ReflectMembers.Concat(expandoMembers).FirstOrDefault(testMember => testMember.DispID == dispid);
                if (!member.IsDefault && RemoveMember(member.Name))
                {
                    return HResult.S_OK;
                }

                // IDispatchEx specifies S_FALSE, but DISP_E_UNKNOWNNAME provides parity with .NET Framework CCWs
                return HResult.DISP_E_UNKNOWNNAME;
            }

            private int GetMemberProperties(int dispid, DispatchPropFlags fetchFlags, out DispatchPropFlags propFlags)
            {
                var member = expandoMembers.FirstOrDefault(testMember => testMember.DispID == dispid);
                if (!member.IsDefault)
                {
                    propFlags =
                        DispatchPropFlags.CanGet |
                        DispatchPropFlags.CanPut |
                        DispatchPropFlags.CannotPutRef |
                        DispatchPropFlags.CannotCall |
                        DispatchPropFlags.CannotConstruct |
                        DispatchPropFlags.CannotSourceEvents;

                    propFlags = propFlags & fetchFlags;
                    return HResult.S_OK;
                }

                propFlags = 0;
                return HResult.DISP_E_MEMBERNOTFOUND;
            }

            private int GetMemberName(int dispid, out string name)
            {
                var member = ReflectMembers.Concat(expandoMembers).OrderBy(testMember => testMember.DispID).FirstOrDefault(testMember => testMember.DispID == dispid);
                if (!member.IsDefault)
                {
                    name = member.Name;
                    return HResult.S_OK;
                }

                name = null;
                return HResult.S_FALSE;
            }

            private int GetNextDispID(int dispidCurrent, out int dispidNext)
            {
                var member = ReflectMembers.Concat(expandoMembers).OrderBy(testMember => testMember.DispID).FirstOrDefault(testMember => testMember.DispID > dispidCurrent);
                if (!member.IsDefault)
                {
                    dispidNext = member.DispID;
                    return HResult.S_OK;
                }

                dispidNext = SpecialDispIDs.Default;
                return HResult.S_FALSE;
            }

            #endregion

            #region Nested type: PatchEntry

            private sealed class PatchEntry
            {
                // ReSharper disable once CollectionNeverQueried.Local
                private readonly List delegates = new List();

                public void AddDelegate(Delegate del)
                {
                    delegates.Add(del);
                }
            }

            #endregion

            #region Nested type: Member

            private struct Member
            {
                public string Name;
                public int DispID;
                public string DispIDName;

                public bool IsDefault
                {
                    get { return DispID == SpecialDispIDs.Default; }
                }
            }

            #endregion
        }

        #endregion
    }
}

Web Proxy Viewer  |  New URL  |  Original Page