1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
5 * This source code is subject to terms and conditions of the Microsoft Public License. A
6 * copy of the license can be found in the License.html file at the root of this distribution. If
7 * you cannot locate the Microsoft Public License, please send an email to
8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
9 * by the terms of the Microsoft Public License.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
15 using System; using Microsoft;
18 #if !SILVERLIGHT // ComObject
20 using System.Collections;
21 using System.Collections.Generic;
22 using System.Diagnostics;
23 using System.Globalization;
25 using System.Linq.Expressions;
27 using Microsoft.Linq.Expressions;
29 using System.Reflection;
30 using System.Runtime.InteropServices;
31 using System.Security;
32 using System.Security.Permissions;
33 using ComTypes = System.Runtime.InteropServices.ComTypes;
36 namespace System.Dynamic {
38 namespace Microsoft.Scripting {
42 /// An object that implements IDispatch
44 /// This currently has the following issues:
45 /// 1. If we prefer ComObjectWithTypeInfo over IDispatchComObject, then we will often not
46 /// IDispatchComObject since implementations of IDispatch often rely on a registered type library.
47 /// If we prefer IDispatchComObject over ComObjectWithTypeInfo, users get a non-ideal experience.
48 /// 2. IDispatch cannot distinguish between properties and methods with 0 arguments (and non-0
49 /// default arguments?). So obj.foo() is ambiguous as it could mean invoking method foo,
50 /// or it could mean invoking the function pointer returned by property foo.
51 /// We are attempting to find whether we need to call a method or a property by examining
52 /// the ITypeInfo associated with the IDispatch. ITypeInfo tell's use what parameters the method
53 /// expects, is it a method or a property, what is the default property of the object, how to
54 /// create an enumerator for collections etc.
55 /// 3. IronPython processes the signature and converts ref arguments into return values.
56 /// However, since the signature of a DispMethod is not available beforehand, this conversion
57 /// is not possible. There could be other signature conversions that may be affected. How does
58 /// VB6 deal with ref arguments and IDispatch?
60 /// We also support events for IDispatch objects:
62 /// COM objects support events through a mechanism known as Connect Points.
63 /// Connection Points are separate objects created off the actual COM
64 /// object (this is to prevent circular references between event sink
65 /// and event source). When clients want to sink events generated by
66 /// COM object they would implement callback interfaces (aka source
67 /// interfaces) and hand it over (advise) to the Connection Point.
69 /// Implementation details:
70 /// When IDispatchComObject.TryGetMember request is received we first check
71 /// whether the requested member is a property or a method. If this check
72 /// fails we will try to determine whether an event is requested. To do
73 /// so we will do the following set of steps:
74 /// 1. Verify the COM object implements IConnectionPointContainer
75 /// 2. Attempt to find COM object's coclass's description
76 /// a. Query the object for IProvideClassInfo interface. Go to 3, if found
77 /// b. From object's IDispatch retrieve primary interface description
78 /// c. Scan coclasses declared in object's type library.
79 /// d. Find coclass implementing this particular primary interface
80 /// 3. Scan coclass for all its source interfaces.
81 /// 4. Check whether to any of the methods on the source interfaces matches
84 /// Once we determine that TryGetMember requests an event we will return
85 /// an instance of BoundDispEvent class. This class has InPlaceAdd and
86 /// InPlaceSubtract operators defined. Calling InPlaceAdd operator will:
87 /// 1. An instance of ComEventSinksContainer class is created (unless
88 /// RCW already had one). This instance is hanged off the RCW in attempt
89 /// to bind the lifetime of event sinks to the lifetime of the RCW itself,
90 /// meaning event sink will be collected once the RCW is collected (this
91 /// is the same way event sinks lifetime is controlled by PIAs).
92 /// Notice: ComEventSinksContainer contains a Finalizer which will go and
93 /// unadvise all event sinks.
94 /// Notice: ComEventSinksContainer is a list of ComEventSink objects.
95 /// 2. Unless we have already created a ComEventSink for the required
96 /// source interface, we will create and advise a new ComEventSink. Each
97 /// ComEventSink implements a single source interface that COM object
99 /// 3. ComEventSink contains a map between method DISPIDs to the
100 /// multicast delegate that will be invoked when the event is raised.
101 /// 4. ComEventSink implements IReflect interface which is exposed as
102 /// custom IDispatch to COM consumers. This allows us to intercept calls
103 /// to IDispatch.Invoke and apply custom logic - in particular we will
104 /// just find and invoke the multicast delegate corresponding to the invoked
108 internal sealed class IDispatchComObject : ComObject, IDynamicMetaObjectProvider {
110 private readonly IDispatch _dispatchObject;
111 private ComTypeDesc _comTypeDesc;
112 private static readonly Dictionary<Guid, ComTypeDesc> _CacheComTypeDesc = new Dictionary<Guid, ComTypeDesc>();
114 internal IDispatchComObject(IDispatch rcw)
116 _dispatchObject = rcw;
119 public override string ToString() {
120 ComTypeDesc ctd = _comTypeDesc;
121 string typeName = null;
124 typeName = ctd.TypeName;
127 if (String.IsNullOrEmpty(typeName)) {
128 typeName = "IDispatch";
131 return String.Format(CultureInfo.CurrentCulture, "{0} ({1})", RuntimeCallableWrapper.ToString(), typeName);
134 public ComTypeDesc ComTypeDesc {
136 EnsureScanDefinedMethods();
141 public IDispatch DispatchObject {
143 return _dispatchObject;
147 private static int GetIDsOfNames(IDispatch dispatch, string name, out int dispId) {
148 int[] dispIds = new int[1];
149 Guid emtpyRiid = Guid.Empty;
150 int hresult = dispatch.TryGetIDsOfNames(
152 new string[] { name },
161 static int Invoke(IDispatch dispatch, int memberDispId, out object result) {
162 Guid emtpyRiid = Guid.Empty;
163 ComTypes.DISPPARAMS dispParams = new ComTypes.DISPPARAMS();
164 ComTypes.EXCEPINFO excepInfo = new ComTypes.EXCEPINFO();
166 int hresult = dispatch.TryInvoke(
170 ComTypes.INVOKEKIND.INVOKE_PROPERTYGET,
179 internal bool TryGetGetItem(out ComMethodDesc value) {
180 ComMethodDesc methodDesc = _comTypeDesc.GetItem;
181 if (methodDesc != null) {
186 return SlowTryGetGetItem(out value);
189 private bool SlowTryGetGetItem(out ComMethodDesc value) {
190 EnsureScanDefinedMethods();
192 ComMethodDesc methodDesc = _comTypeDesc.GetItem;
194 // Without type information, we really don't know whether or not we have a property getter.
195 if (methodDesc == null) {
196 string name = "[PROPERTYGET, DISPID(0)]";
198 _comTypeDesc.EnsureGetItem(new ComMethodDesc(name, ComDispIds.DISPID_VALUE, ComTypes.INVOKEKIND.INVOKE_PROPERTYGET));
199 methodDesc = _comTypeDesc.GetItem;
206 internal bool TryGetSetItem(out ComMethodDesc value) {
207 ComMethodDesc methodDesc = _comTypeDesc.SetItem;
208 if (methodDesc != null) {
213 return SlowTryGetSetItem(out value);
216 private bool SlowTryGetSetItem(out ComMethodDesc value) {
217 EnsureScanDefinedMethods();
219 ComMethodDesc methodDesc = _comTypeDesc.SetItem;
221 // Without type information, we really don't know whether or not we have a property setter.
222 if (methodDesc == null) {
223 string name = "[PROPERTYPUT, DISPID(0)]";
225 _comTypeDesc.EnsureSetItem(new ComMethodDesc(name, ComDispIds.DISPID_VALUE, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT));
226 methodDesc = _comTypeDesc.SetItem;
233 internal bool TryGetMemberMethod(string name, out ComMethodDesc method) {
234 EnsureScanDefinedMethods();
235 return _comTypeDesc.TryGetFunc(name, out method);
238 internal bool TryGetMemberEvent(string name, out ComEventDesc @event) {
239 EnsureScanDefinedEvents();
240 return _comTypeDesc.TryGetEvent(name, out @event);
243 internal bool TryGetMemberMethodExplicit(string name, out ComMethodDesc method) {
244 EnsureScanDefinedMethods();
247 int hresult = GetIDsOfNames(_dispatchObject, name, out dispId);
249 if (hresult == ComHresults.S_OK) {
250 ComMethodDesc cmd = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_FUNC);
251 _comTypeDesc.AddFunc(name, cmd);
254 } else if (hresult == ComHresults.DISP_E_UNKNOWNNAME) {
258 throw Error.CouldNotGetDispId(name, string.Format(CultureInfo.InvariantCulture, "0x{1:X})", hresult));
262 internal bool TryGetPropertySetterExplicit(string name, out ComMethodDesc method, Type limitType, bool holdsNull) {
263 EnsureScanDefinedMethods();
266 int hresult = GetIDsOfNames(_dispatchObject, name, out dispId);
268 if (hresult == ComHresults.S_OK) {
269 // we do not know whether we have put or putref here
270 // and we will not guess and pretend we found both.
271 ComMethodDesc put = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT);
272 _comTypeDesc.AddPut(name, put);
274 ComMethodDesc putref = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF);
275 _comTypeDesc.AddPutRef(name, putref);
277 if (ComBinderHelpers.PreferPut(limitType, holdsNull)) {
283 } else if (hresult == ComHresults.DISP_E_UNKNOWNNAME) {
287 throw Error.CouldNotGetDispId(name, string.Format(CultureInfo.InvariantCulture, "0x{1:X})", hresult));
291 internal override IList<string> GetMemberNames(bool dataOnly) {
292 EnsureScanDefinedMethods();
293 EnsureScanDefinedEvents();
295 return ComTypeDesc.GetMemberNames(dataOnly);
298 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
299 internal override IList<KeyValuePair<string, object>> GetMembers(IEnumerable<string> names) {
301 names = GetMemberNames(true);
304 Type comType = RuntimeCallableWrapper.GetType();
306 var members = new List<KeyValuePair<string, object>>();
307 foreach (string name in names) {
312 ComMethodDesc method;
313 if (ComTypeDesc.TryGetFunc(name, out method) && method.IsDataMember) {
315 object value = comType.InvokeMember(
317 BindingFlags.GetProperty,
319 RuntimeCallableWrapper,
321 CultureInfo.InvariantCulture
323 members.Add(new KeyValuePair<string, object>(method.Name, value));
325 //evaluation failed for some reason. pass exception out
326 } catch (Exception ex) {
327 members.Add(new KeyValuePair<string, object>(method.Name, ex));
332 return members.ToArray();
335 DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) {
336 EnsureScanDefinedMethods();
337 return new IDispatchMetaObject(parameter, this);
340 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
342 private static void GetFuncDescForDescIndex(ComTypes.ITypeInfo typeInfo, int funcIndex, out ComTypes.FUNCDESC funcDesc, out IntPtr funcDescHandle) {
343 IntPtr pFuncDesc = IntPtr.Zero;
344 typeInfo.GetFuncDesc(funcIndex, out pFuncDesc);
346 // GetFuncDesc should never return null, this is just to be safe
347 if (pFuncDesc == IntPtr.Zero) {
348 throw Error.CannotRetrieveTypeInformation();
351 funcDesc = (ComTypes.FUNCDESC)Marshal.PtrToStructure(pFuncDesc, typeof(ComTypes.FUNCDESC));
352 funcDescHandle = pFuncDesc;
356 [SecurityCritical, SecurityTreatAsSafe]
358 [SecuritySafeCritical]
360 private void EnsureScanDefinedEvents() {
361 // _comTypeDesc.Events is null if we have not yet attempted
362 // to scan the object for events.
363 if (_comTypeDesc != null && _comTypeDesc.Events != null) {
368 // Demand Full Trust to proceed with the operation.
371 new PermissionSet(PermissionState.Unrestricted).Demand();
373 // check type info in the type descriptions cache
374 ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(_dispatchObject, true);
375 if (typeInfo == null) {
376 _comTypeDesc = ComTypeDesc.CreateEmptyTypeDesc();
380 ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo);
382 if (_comTypeDesc == null) {
383 lock (_CacheComTypeDesc) {
384 if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) == true &&
385 _comTypeDesc.Events != null) {
391 ComTypeDesc typeDesc = ComTypeDesc.FromITypeInfo(typeInfo, typeAttr);
393 ComTypes.ITypeInfo classTypeInfo = null;
394 Dictionary<string, ComEventDesc> events = null;
396 var cpc = RuntimeCallableWrapper as ComTypes.IConnectionPointContainer;
398 // No ICPC - this object does not support events
399 events = ComTypeDesc.EmptyEvents;
400 } else if ((classTypeInfo = GetCoClassTypeInfo(this.RuntimeCallableWrapper, typeInfo)) == null) {
401 // no class info found - this object may support events
402 // but we could not discover those
403 events = ComTypeDesc.EmptyEvents;
405 events = new Dictionary<string, ComEventDesc>();
407 ComTypes.TYPEATTR classTypeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(classTypeInfo);
408 for (int i = 0; i < classTypeAttr.cImplTypes; i++) {
410 classTypeInfo.GetRefTypeOfImplType(i, out hRefType);
412 ComTypes.ITypeInfo interfaceTypeInfo;
413 classTypeInfo.GetRefTypeInfo(hRefType, out interfaceTypeInfo);
415 ComTypes.IMPLTYPEFLAGS flags;
416 classTypeInfo.GetImplTypeFlags(i, out flags);
417 if ((flags & ComTypes.IMPLTYPEFLAGS.IMPLTYPEFLAG_FSOURCE) != 0) {
418 ScanSourceInterface(interfaceTypeInfo, ref events);
422 if (events.Count == 0) {
423 events = ComTypeDesc.EmptyEvents;
427 lock (_CacheComTypeDesc) {
428 ComTypeDesc cachedTypeDesc;
429 if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out cachedTypeDesc)) {
430 _comTypeDesc = cachedTypeDesc;
432 _comTypeDesc = typeDesc;
433 _CacheComTypeDesc.Add(typeAttr.guid, _comTypeDesc);
435 _comTypeDesc.Events = events;
440 private static void ScanSourceInterface(ComTypes.ITypeInfo sourceTypeInfo, ref Dictionary<string, ComEventDesc> events) {
441 ComTypes.TYPEATTR sourceTypeAttribute = ComRuntimeHelpers.GetTypeAttrForTypeInfo(sourceTypeInfo);
443 for (int index = 0; index < sourceTypeAttribute.cFuncs; index++) {
444 IntPtr funcDescHandleToRelease = IntPtr.Zero;
447 ComTypes.FUNCDESC funcDesc;
448 GetFuncDescForDescIndex(sourceTypeInfo, index, out funcDesc, out funcDescHandleToRelease);
450 // we are not interested in hidden or restricted functions for now.
451 if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FHIDDEN) != 0) {
454 if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FRESTRICTED) != 0) {
458 string name = ComRuntimeHelpers.GetNameOfMethod(sourceTypeInfo, funcDesc.memid);
459 name = name.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
461 // Sometimes coclass has multiple source interfaces. Usually this is caused by
462 // adding new events and putting them on new interfaces while keeping the
463 // old interfaces around. This may cause name collisioning which we are
464 // resolving by keeping only the first event with the same name.
465 if (events.ContainsKey(name) == false) {
466 ComEventDesc eventDesc = new ComEventDesc();
467 eventDesc.dispid = funcDesc.memid;
468 eventDesc.sourceIID = sourceTypeAttribute.guid;
469 events.Add(name, eventDesc);
472 if (funcDescHandleToRelease != IntPtr.Zero) {
473 sourceTypeInfo.ReleaseFuncDesc(funcDescHandleToRelease);
480 private static ComTypes.ITypeInfo GetCoClassTypeInfo(object rcw, ComTypes.ITypeInfo typeInfo) {
481 Debug.Assert(typeInfo != null);
483 IProvideClassInfo provideClassInfo = rcw as IProvideClassInfo;
484 if (provideClassInfo != null) {
485 IntPtr typeInfoPtr = IntPtr.Zero;
487 provideClassInfo.GetClassInfo(out typeInfoPtr);
488 if (typeInfoPtr != IntPtr.Zero) {
489 return Marshal.GetObjectForIUnknown(typeInfoPtr) as ComTypes.ITypeInfo;
492 if (typeInfoPtr != IntPtr.Zero) {
493 Marshal.Release(typeInfoPtr);
498 // retrieving class information through IPCI has failed -
499 // we can try scanning the typelib to find the coclass
501 ComTypes.ITypeLib typeLib;
503 typeInfo.GetContainingTypeLib(out typeLib, out typeInfoIndex);
504 string typeName = ComRuntimeHelpers.GetNameOfType(typeInfo);
506 ComTypeLibDesc typeLibDesc = ComTypeLibDesc.GetFromTypeLib(typeLib);
507 ComTypeClassDesc coclassDesc = typeLibDesc.GetCoClassForInterface(typeName);
508 if (coclassDesc == null) {
512 ComTypes.ITypeInfo typeInfoCoClass;
513 Guid coclassGuid = coclassDesc.Guid;
514 typeLib.GetTypeInfoOfGuid(ref coclassGuid, out typeInfoCoClass);
515 return typeInfoCoClass;
519 [SecurityCritical, SecurityTreatAsSafe]
521 [SecuritySafeCritical]
523 private void EnsureScanDefinedMethods() {
524 if (_comTypeDesc != null && _comTypeDesc.Funcs != null) {
529 // Demand Full Trust to proceed with the operation.
532 new PermissionSet(PermissionState.Unrestricted).Demand();
534 ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(_dispatchObject, true);
535 if (typeInfo == null) {
536 _comTypeDesc = ComTypeDesc.CreateEmptyTypeDesc();
540 ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo);
542 if (_comTypeDesc == null) {
543 lock (_CacheComTypeDesc) {
544 if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) == true &&
545 _comTypeDesc.Funcs != null) {
551 ComTypeDesc typeDesc = ComTypeDesc.FromITypeInfo(typeInfo, typeAttr);
553 ComMethodDesc getItem = null;
554 ComMethodDesc setItem = null;
555 Hashtable funcs = new Hashtable(typeAttr.cFuncs);
556 Hashtable puts = new Hashtable();
557 Hashtable putrefs = new Hashtable();
559 for (int definedFuncIndex = 0; definedFuncIndex < typeAttr.cFuncs; definedFuncIndex++) {
560 IntPtr funcDescHandleToRelease = IntPtr.Zero;
563 ComTypes.FUNCDESC funcDesc;
564 GetFuncDescForDescIndex(typeInfo, definedFuncIndex, out funcDesc, out funcDescHandleToRelease);
566 if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FRESTRICTED) != 0) {
567 // This function is not meant for the script user to use.
571 ComMethodDesc method = new ComMethodDesc(typeInfo, funcDesc);
572 string name = method.Name.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
574 if ((funcDesc.invkind & ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT) != 0) {
575 puts.Add(name, method);
577 // for the special dispId == 0, we need to store
578 // the method descriptor for the Do(SetItem) binder.
579 if (method.DispId == ComDispIds.DISPID_VALUE && setItem == null) {
584 if ((funcDesc.invkind & ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF) != 0) {
585 putrefs.Add(name, method);
586 // for the special dispId == 0, we need to store
587 // the method descriptor for the Do(SetItem) binder.
588 if (method.DispId == ComDispIds.DISPID_VALUE && setItem == null) {
594 if (funcDesc.memid == ComDispIds.DISPID_NEWENUM) {
595 funcs.Add("GETENUMERATOR", method);
599 funcs.Add(name, method);
601 // for the special dispId == 0, we need to store the method descriptor
602 // for the Do(GetItem) binder.
603 if (funcDesc.memid == ComDispIds.DISPID_VALUE) {
607 if (funcDescHandleToRelease != IntPtr.Zero) {
608 typeInfo.ReleaseFuncDesc(funcDescHandleToRelease);
613 lock (_CacheComTypeDesc) {
614 ComTypeDesc cachedTypeDesc;
615 if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out cachedTypeDesc)) {
616 _comTypeDesc = cachedTypeDesc;
618 _comTypeDesc = typeDesc;
619 _CacheComTypeDesc.Add(typeAttr.guid, _comTypeDesc);
621 _comTypeDesc.Funcs = funcs;
622 _comTypeDesc.Puts = puts;
623 _comTypeDesc.PutRefs = putrefs;
624 _comTypeDesc.EnsureGetItem(getItem);
625 _comTypeDesc.EnsureSetItem(setItem);
629 internal bool TryGetPropertySetter(string name, out ComMethodDesc method, Type limitType, bool holdsNull) {
630 EnsureScanDefinedMethods();
632 if (ComBinderHelpers.PreferPut(limitType, holdsNull)) {
633 return _comTypeDesc.TryGetPut(name, out method) ||
634 _comTypeDesc.TryGetPutRef(name, out method);
636 return _comTypeDesc.TryGetPutRef(name, out method) ||
637 _comTypeDesc.TryGetPut(name, out method);