Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
IDispatchComObject.cs686 linesDownload Raw Back to ComInterop
1// Licensed to the .NET Foundation under one or more agreements.2// The .NET Foundation licenses this file to you under the MIT license.3 4using System;5using System.Collections;6using System.Collections.Generic;7using System.Diagnostics;8using System.Dynamic;9using System.Linq.Expressions;10using System.Globalization;11using System.Reflection;12using System.Runtime.InteropServices;13using ComTypes = System.Runtime.InteropServices.ComTypes;14 15namespace System.Management.Automation.ComInterop16{17    /// <summary>18    /// A wrapper around a COM object that implements IDispatch19    ///20    /// This currently has the following issues:21    /// 1. IDispatch cannot distinguish between properties and methods with 0 arguments (and non-022    ///    default arguments?). So obj.foo() is ambiguous as it could mean invoking method foo,23    ///    or it could mean invoking the function pointer returned by property foo.24    ///    We are attempting to find whether we need to call a method or a property by examining25    ///    the ITypeInfo associated with the IDispatch. ITypeInfo tells us what parameters the method26    ///    expects, is it a method or a property, what is the default property of the object, how to27    ///    create an enumerator for collections etc.28    ///29    /// We also support events for IDispatch objects:30    /// Background:31    /// COM objects support events through a mechanism known as Connect Points.32    /// Connection Points are separate objects created off the actual COM33    /// object (this is to prevent circular references between event sink34    /// and event source). When clients want to sink events generated  by35    /// COM object they would implement callback interfaces (aka source36    /// interfaces) and hand it over (advise) to the Connection Point.37    ///38    /// Implementation details:39    /// When IDispatchComObject.TryGetMember request is received we first check40    /// whether the requested member is a property or a method. If this check41    /// fails we will try to determine whether an event is requested. To do42    /// so we will do the following set of steps:43    /// 1. Verify the COM object implements IConnectionPointContainer44    /// 2. Attempt to find COM object's coclass's description45    ///    a. Query the object for IProvideClassInfo interface. Go to 3, if found46    ///    b. From object's IDispatch retrieve primary interface description47    ///    c. Scan coclasses declared in object's type library.48    ///    d. Find coclass implementing this particular primary interface49    /// 3. Scan coclass for all its source interfaces.50    /// 4. Check whether to any of the methods on the source interfaces matches51    /// the request name52    ///53    /// Once we determine that TryGetMember requests an event we will return54    /// an instance of BoundDispEvent class. This class has InPlaceAdd and55    /// InPlaceSubtract operators defined. Calling InPlaceAdd operator will:56    /// 1. An instance of ComEventSinksContainer class is created (unless57    /// RCW already had one). This instance is associated to the RCW in attempt58    /// to bind the lifetime of event sinks to the lifetime of the RCW itself,59    /// meaning event sink will be collected once the RCW is collected (this60    /// is the same way event sinks lifetime is controlled by PIAs).61    /// Notice: ComEventSinksContainer contains a Finalizer which will go and62    /// unadvise all event sinks.63    /// Notice: ComEventSinksContainer is a list of ComEventSink objects.64    /// 2. Unless we have already created a ComEventSink for the required65    /// source interface, we will create and advise a new ComEventSink. Each66    /// ComEventSink implements a single source interface that COM object67    /// supports.68    /// 3. ComEventSink contains a map between method DISPIDs to  the69    /// multicast delegate that will be invoked when the event is raised.70    /// 4. ComEventSink implements IReflect interface which is exposed as71    /// custom IDispatch to COM consumers. This allows us to intercept calls72    /// to IDispatch.Invoke and apply custom logic - in particular we will73    /// just find and invoke the multicast delegate corresponding to the invoked74    /// dispid.75    ///  </summary>76    internal sealed class IDispatchComObject : ComObject, IDynamicMetaObjectProvider77    {78        private ComTypeDesc _comTypeDesc;79        private static readonly Dictionary<Guid, ComTypeDesc> s_cacheComTypeDesc = new Dictionary<Guid, ComTypeDesc>();80 81        internal IDispatchComObject(IDispatch rcw)82            : base(rcw)83        {84            DispatchObject = rcw;85        }86 87        public override string ToString()88        {89            ComTypeDesc ctd = _comTypeDesc;90            string typeName = null;91 92            if (ctd != null)93            {94                typeName = ctd.TypeName;95            }96 97            if (string.IsNullOrEmpty(typeName))98            {99                typeName = "IDispatch";100            }101 102            return $"{RuntimeCallableWrapper} ({typeName})";103        }104 105        public ComTypeDesc ComTypeDesc106        {107            get108            {109                EnsureScanDefinedMethods();110                return _comTypeDesc;111            }112        }113 114        public IDispatch DispatchObject { get; }115 116        private static int GetIDsOfNames(IDispatch dispatch, string name, out int dispId)117        {118            int[] dispIds = new int[1];119            Guid emptyRiid = Guid.Empty;120            int hresult = dispatch.TryGetIDsOfNames(121                ref emptyRiid,122                new string[] { name },123                1,124                0,125                dispIds);126 127            dispId = dispIds[0];128            return hresult;129        }130 131        internal bool TryGetGetItem(out ComMethodDesc value)132        {133            ComMethodDesc methodDesc = _comTypeDesc.GetItem;134            if (methodDesc != null)135            {136                value = methodDesc;137                return true;138            }139 140            return SlowTryGetGetItem(out value);141        }142 143        private bool SlowTryGetGetItem(out ComMethodDesc value)144        {145            EnsureScanDefinedMethods();146 147            ComMethodDesc methodDesc = _comTypeDesc.GetItem;148 149            // Without type information, we really don't know whether or not we have a property getter.150            if (methodDesc == null)151            {152                string name = "[PROPERTYGET, DISPID(0)]";153 154                _comTypeDesc.EnsureGetItem(new ComMethodDesc(name, ComDispIds.DISPID_VALUE, ComTypes.INVOKEKIND.INVOKE_PROPERTYGET));155                methodDesc = _comTypeDesc.GetItem;156            }157 158            value = methodDesc;159            return true;160        }161 162        internal bool TryGetSetItem(out ComMethodDesc value)163        {164            ComMethodDesc methodDesc = _comTypeDesc.SetItem;165            if (methodDesc != null)166            {167                value = methodDesc;168                return true;169            }170 171            return SlowTryGetSetItem(out value);172        }173 174        private bool SlowTryGetSetItem(out ComMethodDesc value)175        {176            EnsureScanDefinedMethods();177 178            ComMethodDesc methodDesc = _comTypeDesc.SetItem;179 180            // Without type information, we really don't know whether or not we have a property setter.181            if (methodDesc == null)182            {183                string name = "[PROPERTYPUT, DISPID(0)]";184 185                _comTypeDesc.EnsureSetItem(new ComMethodDesc(name, ComDispIds.DISPID_VALUE, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT));186                methodDesc = _comTypeDesc.SetItem;187            }188 189            value = methodDesc;190            return true;191        }192 193        internal bool TryGetMemberMethod(string name, out ComMethodDesc method)194        {195            EnsureScanDefinedMethods();196            return _comTypeDesc.TryGetFunc(name, out method);197        }198 199        internal bool TryGetMemberEvent(string name, out ComEventDesc @event)200        {201            EnsureScanDefinedEvents();202            return _comTypeDesc.TryGetEvent(name, out @event);203        }204 205        internal bool TryGetMemberMethodExplicit(string name, out ComMethodDesc method)206        {207            EnsureScanDefinedMethods();208 209            int hresult = GetIDsOfNames(DispatchObject, name, out int dispId);210 211            if (hresult == ComHresults.S_OK)212            {213                ComMethodDesc cmd = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_FUNC);214                _comTypeDesc.AddFunc(name, cmd);215                method = cmd;216                return true;217            }218 219            if (hresult == ComHresults.DISP_E_UNKNOWNNAME)220            {221                method = null;222                return false;223            }224 225            throw Error.CouldNotGetDispId(name, string.Create(CultureInfo.InvariantCulture, $"0x{hresult:X})"));226        }227 228        internal bool TryGetPropertySetterExplicit(string name, out ComMethodDesc method, Type limitType, bool holdsNull)229        {230            EnsureScanDefinedMethods();231 232            int hresult = GetIDsOfNames(DispatchObject, name, out int dispId);233 234            if (hresult == ComHresults.S_OK)235            {236                // we do not know whether we have put or putref here237                // and we will not guess and pretend we found both.238                ComMethodDesc put = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT);239                _comTypeDesc.AddPut(name, put);240 241                ComMethodDesc putref = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF);242                _comTypeDesc.AddPutRef(name, putref);243 244                if (ComBinderHelpers.PreferPut(limitType, holdsNull))245                {246                    method = put;247                }248                else249                {250                    method = putref;251                }252                return true;253            }254 255            if (hresult == ComHresults.DISP_E_UNKNOWNNAME)256            {257                method = null;258                return false;259            }260 261            throw Error.CouldNotGetDispId(name, string.Create(CultureInfo.InvariantCulture, $"0x{hresult:X})"));262        }263 264        internal override IList<string> GetMemberNames(bool dataOnly)265        {266            EnsureScanDefinedMethods();267            EnsureScanDefinedEvents();268 269            return ComTypeDesc.GetMemberNames(dataOnly);270        }271 272        internal override IList<KeyValuePair<string, object>> GetMembers(IEnumerable<string> names)273        {274            names ??= GetMemberNames(true);275 276            Type comType = RuntimeCallableWrapper.GetType();277 278            var members = new List<KeyValuePair<string, object>>();279            foreach (string name in names)280            {281                if (name == null)282                {283                    continue;284                }285 286                if (ComTypeDesc.TryGetFunc(name, out ComMethodDesc method) && method.IsDataMember)287                {288                    try289                    {290                        object value = comType.InvokeMember(291                            method.Name,292                            BindingFlags.GetProperty,293                            null,294                            RuntimeCallableWrapper,295                            Array.Empty<object>(),296                            CultureInfo.InvariantCulture297                        );298                        members.Add(new KeyValuePair<string, object>(method.Name, value));299 300                        //evaluation failed for some reason. pass exception out301                    }302                    catch (Exception ex)303                    {304                        members.Add(new KeyValuePair<string, object>(method.Name, ex));305                    }306                }307            }308 309            return members.ToArray();310        }311 312        DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter)313        {314            EnsureScanDefinedMethods();315            return new IDispatchMetaObject(parameter, this);316        }317 318        private static void GetFuncDescForDescIndex(ComTypes.ITypeInfo typeInfo, int funcIndex, out ComTypes.FUNCDESC funcDesc, out IntPtr funcDescHandle)319        {320            IntPtr pFuncDesc;321            typeInfo.GetFuncDesc(funcIndex, out pFuncDesc);322 323            // GetFuncDesc should never return null, this is just to be safe324            if (pFuncDesc == IntPtr.Zero)325            {326                throw Error.CannotRetrieveTypeInformation();327            }328 329            funcDesc = (ComTypes.FUNCDESC)Marshal.PtrToStructure(pFuncDesc, typeof(ComTypes.FUNCDESC));330            funcDescHandle = pFuncDesc;331        }332 333        private void EnsureScanDefinedEvents()334        {335            // _comTypeDesc.Events is null if we have not yet attempted336            // to scan the object for events.337            if (_comTypeDesc?.Events != null)338            {339                return;340            }341 342            // check type info in the type descriptions cache343            ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(DispatchObject);344            if (typeInfo == null)345            {346                _comTypeDesc = ComTypeDesc.CreateEmptyTypeDesc();347                return;348            }349 350            ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo);351 352            if (_comTypeDesc == null)353            {354                lock (s_cacheComTypeDesc)355                {356                    if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) &&357                        _comTypeDesc.Events != null)358                    {359                        return;360                    }361                }362            }363 364            ComTypeDesc typeDesc = ComTypeDesc.FromITypeInfo(typeInfo, typeAttr);365 366            ComTypes.ITypeInfo classTypeInfo;367            Dictionary<string, ComEventDesc> events;368 369            var cpc = RuntimeCallableWrapper as ComTypes.IConnectionPointContainer;370            if (cpc == null)371            {372                // No ICPC - this object does not support events373                events = ComTypeDesc.EmptyEvents;374            }375            else if ((classTypeInfo = GetCoClassTypeInfo(RuntimeCallableWrapper, typeInfo)) == null)376            {377                // no class info found - this object may support events378                // but we could not discover those379                events = ComTypeDesc.EmptyEvents;380            }381            else382            {383                events = new Dictionary<string, ComEventDesc>();384 385                ComTypes.TYPEATTR classTypeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(classTypeInfo);386                for (int i = 0; i < classTypeAttr.cImplTypes; i++)387                {388                    classTypeInfo.GetRefTypeOfImplType(i, out int hRefType);389 390                    classTypeInfo.GetRefTypeInfo(hRefType, out ComTypes.ITypeInfo interfaceTypeInfo);391 392                    classTypeInfo.GetImplTypeFlags(i, out ComTypes.IMPLTYPEFLAGS flags);393                    if ((flags & ComTypes.IMPLTYPEFLAGS.IMPLTYPEFLAG_FSOURCE) != 0)394                    {395                        ScanSourceInterface(interfaceTypeInfo, ref events);396                    }397                }398 399                if (events.Count == 0)400                {401                    events = ComTypeDesc.EmptyEvents;402                }403            }404 405            lock (s_cacheComTypeDesc)406            {407                if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out ComTypeDesc cachedTypeDesc))408                {409                    _comTypeDesc = cachedTypeDesc;410                }411                else412                {413                    _comTypeDesc = typeDesc;414                    s_cacheComTypeDesc.Add(typeAttr.guid, _comTypeDesc);415                }416                _comTypeDesc.Events = events;417            }418        }419 420        private static void ScanSourceInterface(ComTypes.ITypeInfo sourceTypeInfo, ref Dictionary<string, ComEventDesc> events)421        {422            ComTypes.TYPEATTR sourceTypeAttribute = ComRuntimeHelpers.GetTypeAttrForTypeInfo(sourceTypeInfo);423 424            for (int index = 0; index < sourceTypeAttribute.cFuncs; index++)425            {426                IntPtr funcDescHandleToRelease = IntPtr.Zero;427 428                try429                {430                    GetFuncDescForDescIndex(sourceTypeInfo, index, out ComTypes.FUNCDESC funcDesc, out funcDescHandleToRelease);431 432                    // we are not interested in hidden or restricted functions for now.433                    if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FHIDDEN) != 0)434                    {435                        continue;436                    }437                    if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FRESTRICTED) != 0)438                    {439                        continue;440                    }441 442                    string name = ComRuntimeHelpers.GetNameOfMethod(sourceTypeInfo, funcDesc.memid);443                    name = name.ToUpper(System.Globalization.CultureInfo.InvariantCulture);444 445                    // Sometimes coclass has multiple source interfaces. Usually this is caused by446                    // adding new events and putting them on new interfaces while keeping the447                    // old interfaces around. This may cause name collisions which we are448                    // resolving by keeping only the first event with the same name.449                    if (!events.ContainsKey(name))450                    {451                        ComEventDesc eventDesc = new ComEventDesc452                        {453                            Dispid = funcDesc.memid,454                            SourceIID = sourceTypeAttribute.guid455                        };456                        events.Add(name, eventDesc);457                    }458                }459                finally460                {461                    if (funcDescHandleToRelease != IntPtr.Zero)462                    {463                        sourceTypeInfo.ReleaseFuncDesc(funcDescHandleToRelease);464                    }465                }466            }467        }468 469        private static ComTypes.ITypeInfo GetCoClassTypeInfo(object rcw, ComTypes.ITypeInfo typeInfo)470        {471            Debug.Assert(typeInfo != null);472 473            if (rcw is IProvideClassInfo provideClassInfo)474            {475                IntPtr typeInfoPtr = IntPtr.Zero;476                try477                {478                    provideClassInfo.GetClassInfo(out typeInfoPtr);479                    if (typeInfoPtr != IntPtr.Zero)480                    {481                        return Marshal.GetObjectForIUnknown(typeInfoPtr) as ComTypes.ITypeInfo;482                    }483                }484                finally485                {486                    if (typeInfoPtr != IntPtr.Zero)487                    {488                        Marshal.Release(typeInfoPtr);489                    }490                }491            }492 493            // retrieving class information through IPCI has failed -494            // we can try scanning the typelib to find the coclass495 496            typeInfo.GetContainingTypeLib(out ComTypes.ITypeLib typeLib, out int _);497            string typeName = ComRuntimeHelpers.GetNameOfType(typeInfo);498 499            ComTypeLibDesc typeLibDesc = ComTypeLibDesc.GetFromTypeLib(typeLib);500            ComTypeClassDesc coclassDesc = typeLibDesc.GetCoClassForInterface(typeName);501            if (coclassDesc == null)502            {503                return null;504            }505 506            Guid coclassGuid = coclassDesc.Guid;507            typeLib.GetTypeInfoOfGuid(ref coclassGuid, out ComTypes.ITypeInfo typeInfoCoClass);508            return typeInfoCoClass;509        }510 511        private void EnsureScanDefinedMethods()512        {513            if (_comTypeDesc?.Funcs != null)514            {515                return;516            }517 518            ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(DispatchObject);519            if (typeInfo == null)520            {521                _comTypeDesc = ComTypeDesc.CreateEmptyTypeDesc();522                return;523            }524 525            ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo);526 527            if (_comTypeDesc == null)528            {529                lock (s_cacheComTypeDesc)530                {531                    if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) &&532                        _comTypeDesc.Funcs != null)533                    {534                        return;535                    }536                }537            }538 539            if (typeAttr.typekind == ComTypes.TYPEKIND.TKIND_INTERFACE)540            {541                // We have typeinfo for custom interface. Get typeinfo for Dispatch interface.542                typeInfo = ComTypeInfo.GetDispatchTypeInfoFromCustomInterfaceTypeInfo(typeInfo);543                typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo);544            }545 546            if (typeAttr.typekind == ComTypes.TYPEKIND.TKIND_COCLASS)547            {548                // We have typeinfo for the COClass.  Find the default interface and get typeinfo for default interface.549                typeInfo = ComTypeInfo.GetDispatchTypeInfoFromCoClassTypeInfo(typeInfo);550                typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo);551            }552 553            ComTypeDesc typeDesc = ComTypeDesc.FromITypeInfo(typeInfo, typeAttr);554 555            ComMethodDesc getItem = null;556            ComMethodDesc setItem = null;557            Hashtable funcs = new Hashtable(typeAttr.cFuncs);558            Hashtable puts = new Hashtable();559            Hashtable putrefs = new Hashtable();560 561            for (int definedFuncIndex = 0; definedFuncIndex < typeAttr.cFuncs; definedFuncIndex++)562            {563                IntPtr funcDescHandleToRelease = IntPtr.Zero;564 565                try566                {567                    GetFuncDescForDescIndex(typeInfo, definedFuncIndex, out ComTypes.FUNCDESC funcDesc, out funcDescHandleToRelease);568 569                    if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FRESTRICTED) != 0)570                    {571                        // This function is not meant for the script user to use.572                        continue;573                    }574 575                    ComMethodDesc method = new ComMethodDesc(typeInfo, funcDesc);576                    string name = method.Name.ToUpper(CultureInfo.InvariantCulture);577 578                    if ((funcDesc.invkind & ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT) != 0)579                    {580                        // If there is a getter for this put, use that ReturnType as the581                        // PropertyType.582                        if (funcs.ContainsKey(name))583                        {584                            method.InputType = ((ComMethodDesc)funcs[name]).ReturnType;585                        }586 587                        puts.Add(name, method);588 589                        // for the special dispId == 0, we need to store590                        // the method descriptor for the Do(SetItem) binder.591                        if (method.DispId == ComDispIds.DISPID_VALUE && setItem == null)592                        {593                            setItem = method;594                        }595                        continue;596                    }597                    if ((funcDesc.invkind & ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF) != 0)598                    {599                        // If there is a getter for this put, use that ReturnType as the600                        // PropertyType.601                        if (funcs.ContainsKey(name))602                        {603                            method.InputType = ((ComMethodDesc)funcs[name]).ReturnType;604                        }605 606                        putrefs.Add(name, method);607                        // for the special dispId == 0, we need to store608                        // the method descriptor for the Do(SetItem) binder.609                        if (method.DispId == ComDispIds.DISPID_VALUE && setItem == null)610                        {611                            setItem = method;612                        }613                        continue;614                    }615 616                    if (funcDesc.memid == ComDispIds.DISPID_NEWENUM)617                    {618                        funcs.Add("GETENUMERATOR", method);619                        continue;620                    }621 622                    // If there is a setter for this put, update the InputType from our623                    // ReturnType.624                    if (puts.ContainsKey(name))625                    {626                        ((ComMethodDesc)puts[name]).InputType = method.ReturnType;627                    }628 629                    if (putrefs.ContainsKey(name))630                    {631                        ((ComMethodDesc)putrefs[name]).InputType = method.ReturnType;632                    }633 634                    funcs.Add(name, method);635 636                    // for the special dispId == 0, we need to store the method descriptor637                    // for the Do(GetItem) binder.638                    if (funcDesc.memid == ComDispIds.DISPID_VALUE)639                    {640                        getItem = method;641                    }642                }643                finally644                {645                    if (funcDescHandleToRelease != IntPtr.Zero)646                    {647                        typeInfo.ReleaseFuncDesc(funcDescHandleToRelease);648                    }649                }650            }651 652            lock (s_cacheComTypeDesc)653            {654                if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out ComTypeDesc cachedTypeDesc))655                {656                    _comTypeDesc = cachedTypeDesc;657                }658                else659                {660                    _comTypeDesc = typeDesc;661                    s_cacheComTypeDesc.Add(typeAttr.guid, _comTypeDesc);662                }663                _comTypeDesc.Funcs = funcs;664                _comTypeDesc.Puts = puts;665                _comTypeDesc.PutRefs = putrefs;666                _comTypeDesc.EnsureGetItem(getItem);667                _comTypeDesc.EnsureSetItem(setItem);668            }669        }670 671        internal bool TryGetPropertySetter(string name, out ComMethodDesc method, Type limitType, bool holdsNull)672        {673            EnsureScanDefinedMethods();674 675            if (ComBinderHelpers.PreferPut(limitType, holdsNull))676            {677                return _comTypeDesc.TryGetPut(name, out method) ||678                    _comTypeDesc.TryGetPutRef(name, out method);679            }680 681            return _comTypeDesc.TryGetPutRef(name, out method) ||682                _comTypeDesc.TryGetPut(name, out method);683        }684    }685}686