MegaBites-AI/Windows-powershell
0372
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 