Merge pull request #1109 from adbre/iss358
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Dynamic / IDispatchComObject.cs
1 /* ****************************************************************************
2  *
3  * Copyright (c) Microsoft Corporation. 
4  *
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.
10  *
11  * You must not remove this notice, or any other, from this software.
12  *
13  *
14  * ***************************************************************************/
15 using System; using Microsoft;
16
17
18 #if !SILVERLIGHT // ComObject
19
20 using System.Collections;
21 using System.Collections.Generic;
22 using System.Diagnostics;
23 using System.Globalization;
24 #if CODEPLEX_40
25 using System.Linq.Expressions;
26 #else
27 using Microsoft.Linq.Expressions;
28 #endif
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;
34
35 #if CODEPLEX_40
36 namespace System.Dynamic {
37 #else
38 namespace Microsoft.Scripting {
39 #endif
40
41     /// <summary>
42     /// An object that implements IDispatch
43     /// 
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?
59     ///    
60     /// We also support events for IDispatch objects:
61     /// Background:
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. 
68     /// 
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 
82     /// the request name
83     /// 
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 
98     /// supports. 
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
105     /// dispid.
106     ///  </summary>
107
108     internal sealed class IDispatchComObject : ComObject, IDynamicMetaObjectProvider {
109
110         private readonly IDispatch _dispatchObject;
111         private ComTypeDesc _comTypeDesc;
112         private static readonly Dictionary<Guid, ComTypeDesc> _CacheComTypeDesc = new Dictionary<Guid, ComTypeDesc>();
113
114         internal IDispatchComObject(IDispatch rcw)
115             : base(rcw) {
116             _dispatchObject = rcw;
117         }
118
119         public override string ToString() {
120             ComTypeDesc ctd = _comTypeDesc;
121             string typeName = null;
122
123             if (ctd != null) {
124                 typeName = ctd.TypeName;
125             }
126
127             if (String.IsNullOrEmpty(typeName)) {
128                 typeName = "IDispatch";
129             }
130
131             return String.Format(CultureInfo.CurrentCulture, "{0} ({1})", RuntimeCallableWrapper.ToString(), typeName);
132         }
133
134         public ComTypeDesc ComTypeDesc {
135             get {
136                 EnsureScanDefinedMethods();
137                 return _comTypeDesc;
138             }
139         }
140
141         public IDispatch DispatchObject {
142             get {
143                 return _dispatchObject;
144             }
145         }
146
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(
151                 ref emtpyRiid,
152                 new string[] { name },
153                 1,
154                 0,
155                 dispIds);
156
157             dispId = dispIds[0];
158             return hresult;
159         }
160
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();
165             uint argErr;
166             int hresult = dispatch.TryInvoke(
167                 memberDispId,
168                 ref emtpyRiid,
169                 0,
170                 ComTypes.INVOKEKIND.INVOKE_PROPERTYGET,
171                 ref dispParams,
172                 out result,
173                 out excepInfo,
174                 out argErr);
175
176             return hresult;
177         }
178
179         internal bool TryGetGetItem(out ComMethodDesc value) {
180             ComMethodDesc methodDesc = _comTypeDesc.GetItem;
181             if (methodDesc != null) {
182                 value = methodDesc;
183                 return true;
184             }
185
186             return SlowTryGetGetItem(out value);
187         }
188
189         private bool SlowTryGetGetItem(out ComMethodDesc value) {
190             EnsureScanDefinedMethods();
191
192             ComMethodDesc methodDesc = _comTypeDesc.GetItem;
193
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)]";
197
198                 _comTypeDesc.EnsureGetItem(new ComMethodDesc(name, ComDispIds.DISPID_VALUE, ComTypes.INVOKEKIND.INVOKE_PROPERTYGET));
199                 methodDesc = _comTypeDesc.GetItem;
200             }
201
202             value = methodDesc;
203             return true;
204         }
205
206         internal bool TryGetSetItem(out ComMethodDesc value) {
207             ComMethodDesc methodDesc = _comTypeDesc.SetItem;
208             if (methodDesc != null) {
209                 value = methodDesc;
210                 return true;
211             }
212
213             return SlowTryGetSetItem(out value);
214         }
215
216         private bool SlowTryGetSetItem(out ComMethodDesc value) {
217             EnsureScanDefinedMethods();
218
219             ComMethodDesc methodDesc = _comTypeDesc.SetItem;
220
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)]";
224
225                 _comTypeDesc.EnsureSetItem(new ComMethodDesc(name, ComDispIds.DISPID_VALUE, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT));
226                 methodDesc = _comTypeDesc.SetItem;
227             }
228
229             value = methodDesc;
230             return true;
231         }
232
233         internal bool TryGetMemberMethod(string name, out ComMethodDesc method) {
234             EnsureScanDefinedMethods();
235             return _comTypeDesc.TryGetFunc(name, out method);
236         }
237
238         internal bool TryGetMemberEvent(string name, out ComEventDesc @event) {
239             EnsureScanDefinedEvents();
240             return _comTypeDesc.TryGetEvent(name, out @event);
241         }
242
243         internal bool TryGetMemberMethodExplicit(string name, out ComMethodDesc method) {
244             EnsureScanDefinedMethods();
245
246             int dispId;
247             int hresult = GetIDsOfNames(_dispatchObject, name, out dispId);
248
249             if (hresult == ComHresults.S_OK) {
250                 ComMethodDesc cmd = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_FUNC);
251                 _comTypeDesc.AddFunc(name, cmd);
252                 method = cmd;
253                 return true;
254             } else if (hresult == ComHresults.DISP_E_UNKNOWNNAME) {
255                 method = null;
256                 return false;
257             } else {
258                 throw Error.CouldNotGetDispId(name, string.Format(CultureInfo.InvariantCulture, "0x{1:X})", hresult));
259             }
260         }
261
262         internal bool TryGetPropertySetterExplicit(string name, out ComMethodDesc method, Type limitType, bool holdsNull) {
263             EnsureScanDefinedMethods();
264
265             int dispId;
266             int hresult = GetIDsOfNames(_dispatchObject, name, out dispId);
267
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);
273
274                 ComMethodDesc putref = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF);
275                 _comTypeDesc.AddPutRef(name, putref);
276
277                 if (ComBinderHelpers.PreferPut(limitType, holdsNull)) {
278                     method = put;
279                 } else {
280                     method = putref;
281                 }
282                 return true;
283             } else if (hresult == ComHresults.DISP_E_UNKNOWNNAME) {
284                 method = null;
285                 return false;
286             } else {
287                 throw Error.CouldNotGetDispId(name, string.Format(CultureInfo.InvariantCulture, "0x{1:X})", hresult));
288             }
289         }
290
291         internal override IList<string> GetMemberNames(bool dataOnly) {
292             EnsureScanDefinedMethods();
293             EnsureScanDefinedEvents();
294
295             return ComTypeDesc.GetMemberNames(dataOnly);
296         }
297
298         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
299         internal override IList<KeyValuePair<string, object>> GetMembers(IEnumerable<string> names) {
300             if (names == null) {
301                 names = GetMemberNames(true);
302             }
303
304             Type comType = RuntimeCallableWrapper.GetType();
305
306             var members = new List<KeyValuePair<string, object>>();
307             foreach (string name in names) {
308                 if (name == null) {
309                     continue;
310                 }
311
312                 ComMethodDesc method;
313                 if (ComTypeDesc.TryGetFunc(name, out method) && method.IsDataMember) {
314                     try {
315                         object value = comType.InvokeMember(
316                             method.Name,
317                             BindingFlags.GetProperty,
318                             null,
319                             RuntimeCallableWrapper,
320                             new object[0],
321                             CultureInfo.InvariantCulture
322                         );
323                         members.Add(new KeyValuePair<string, object>(method.Name, value));
324
325                         //evaluation failed for some reason. pass exception out 
326                     } catch (Exception ex) {
327                         members.Add(new KeyValuePair<string, object>(method.Name, ex));
328                     }
329                 }
330             }
331
332             return members.ToArray();
333         }
334
335         DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) {
336             EnsureScanDefinedMethods();
337             return new IDispatchMetaObject(parameter, this);
338         }
339
340         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
341         [SecurityCritical]
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);
345
346             // GetFuncDesc should never return null, this is just to be safe
347             if (pFuncDesc == IntPtr.Zero) {
348                 throw Error.CannotRetrieveTypeInformation();
349             }
350
351             funcDesc = (ComTypes.FUNCDESC)Marshal.PtrToStructure(pFuncDesc, typeof(ComTypes.FUNCDESC));
352             funcDescHandle = pFuncDesc;
353         }
354
355 #if CLR2
356         [SecurityCritical, SecurityTreatAsSafe]
357 #else
358         [SecuritySafeCritical]
359 #endif
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) {
364                 return;
365             }
366
367             //
368             // Demand Full Trust to proceed with the operation.
369             //
370
371             new PermissionSet(PermissionState.Unrestricted).Demand();
372
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();
377                 return;
378             }
379
380             ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo);
381
382             if (_comTypeDesc == null) {
383                 lock (_CacheComTypeDesc) {
384                     if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) == true &&
385                         _comTypeDesc.Events != null) {
386                         return;
387                     }
388                 }
389             }
390
391             ComTypeDesc typeDesc = ComTypeDesc.FromITypeInfo(typeInfo, typeAttr);
392
393             ComTypes.ITypeInfo classTypeInfo = null;
394             Dictionary<string, ComEventDesc> events = null;
395
396             var cpc = RuntimeCallableWrapper as ComTypes.IConnectionPointContainer;
397             if (cpc == null) {
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;
404             } else {
405                 events = new Dictionary<string, ComEventDesc>();
406
407                 ComTypes.TYPEATTR classTypeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(classTypeInfo);
408                 for (int i = 0; i < classTypeAttr.cImplTypes; i++) {
409                     int hRefType;
410                     classTypeInfo.GetRefTypeOfImplType(i, out hRefType);
411
412                     ComTypes.ITypeInfo interfaceTypeInfo;
413                     classTypeInfo.GetRefTypeInfo(hRefType, out interfaceTypeInfo);
414
415                     ComTypes.IMPLTYPEFLAGS flags;
416                     classTypeInfo.GetImplTypeFlags(i, out flags);
417                     if ((flags & ComTypes.IMPLTYPEFLAGS.IMPLTYPEFLAG_FSOURCE) != 0) {
418                         ScanSourceInterface(interfaceTypeInfo, ref events);
419                     }
420                 }
421
422                 if (events.Count == 0) {
423                     events = ComTypeDesc.EmptyEvents;
424                 }
425             }
426
427             lock (_CacheComTypeDesc) {
428                 ComTypeDesc cachedTypeDesc;
429                 if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out cachedTypeDesc)) {
430                     _comTypeDesc = cachedTypeDesc;
431                 } else {
432                     _comTypeDesc = typeDesc;
433                     _CacheComTypeDesc.Add(typeAttr.guid, _comTypeDesc);
434                 }
435                 _comTypeDesc.Events = events;
436             }
437         }
438
439         [SecurityCritical]
440         private static void ScanSourceInterface(ComTypes.ITypeInfo sourceTypeInfo, ref Dictionary<string, ComEventDesc> events) {
441             ComTypes.TYPEATTR sourceTypeAttribute = ComRuntimeHelpers.GetTypeAttrForTypeInfo(sourceTypeInfo);
442
443             for (int index = 0; index < sourceTypeAttribute.cFuncs; index++) {
444                 IntPtr funcDescHandleToRelease = IntPtr.Zero;
445
446                 try {
447                     ComTypes.FUNCDESC funcDesc;
448                     GetFuncDescForDescIndex(sourceTypeInfo, index, out funcDesc, out funcDescHandleToRelease);
449
450                     // we are not interested in hidden or restricted functions for now.
451                     if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FHIDDEN) != 0) {
452                         continue;
453                     }
454                     if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FRESTRICTED) != 0) {
455                         continue;
456                     }
457
458                     string name = ComRuntimeHelpers.GetNameOfMethod(sourceTypeInfo, funcDesc.memid);
459                     name = name.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
460
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);
470                     }
471                 } finally {
472                     if (funcDescHandleToRelease != IntPtr.Zero) {
473                         sourceTypeInfo.ReleaseFuncDesc(funcDescHandleToRelease);
474                     }
475                 }
476             }
477         }
478
479         [SecurityCritical]
480         private static ComTypes.ITypeInfo GetCoClassTypeInfo(object rcw, ComTypes.ITypeInfo typeInfo) {
481             Debug.Assert(typeInfo != null);
482
483             IProvideClassInfo provideClassInfo = rcw as IProvideClassInfo;
484             if (provideClassInfo != null) {
485                 IntPtr typeInfoPtr = IntPtr.Zero;
486                 try {
487                     provideClassInfo.GetClassInfo(out typeInfoPtr);
488                     if (typeInfoPtr != IntPtr.Zero) {
489                         return Marshal.GetObjectForIUnknown(typeInfoPtr) as ComTypes.ITypeInfo;
490                     }
491                 } finally {
492                     if (typeInfoPtr != IntPtr.Zero) {
493                         Marshal.Release(typeInfoPtr);
494                     }
495                 }
496             }
497
498             // retrieving class information through IPCI has failed - 
499             // we can try scanning the typelib to find the coclass
500
501             ComTypes.ITypeLib typeLib;
502             int typeInfoIndex;
503             typeInfo.GetContainingTypeLib(out typeLib, out typeInfoIndex);
504             string typeName = ComRuntimeHelpers.GetNameOfType(typeInfo);
505
506             ComTypeLibDesc typeLibDesc = ComTypeLibDesc.GetFromTypeLib(typeLib);
507             ComTypeClassDesc coclassDesc = typeLibDesc.GetCoClassForInterface(typeName);
508             if (coclassDesc == null) {
509                 return null;
510             }
511
512             ComTypes.ITypeInfo typeInfoCoClass;
513             Guid coclassGuid = coclassDesc.Guid;
514             typeLib.GetTypeInfoOfGuid(ref coclassGuid, out typeInfoCoClass);
515             return typeInfoCoClass;
516         }
517
518 #if CLR2
519         [SecurityCritical, SecurityTreatAsSafe]
520 #else
521         [SecuritySafeCritical]
522 #endif
523         private void EnsureScanDefinedMethods() {
524             if (_comTypeDesc != null && _comTypeDesc.Funcs != null) {
525                 return;
526             }
527
528             //
529             // Demand Full Trust to proceed with the operation.
530             //
531
532             new PermissionSet(PermissionState.Unrestricted).Demand();
533
534             ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(_dispatchObject, true);
535             if (typeInfo == null) {
536                 _comTypeDesc = ComTypeDesc.CreateEmptyTypeDesc();
537                 return;
538             }
539
540             ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo);
541
542             if (_comTypeDesc == null) {
543                 lock (_CacheComTypeDesc) {
544                     if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) == true &&
545                         _comTypeDesc.Funcs != null) {
546                         return;
547                     }
548                 }
549             }
550
551             ComTypeDesc typeDesc = ComTypeDesc.FromITypeInfo(typeInfo, typeAttr);
552
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();
558
559             for (int definedFuncIndex = 0; definedFuncIndex < typeAttr.cFuncs; definedFuncIndex++) {
560                 IntPtr funcDescHandleToRelease = IntPtr.Zero;
561
562                 try {
563                     ComTypes.FUNCDESC funcDesc;
564                     GetFuncDescForDescIndex(typeInfo, definedFuncIndex, out funcDesc, out funcDescHandleToRelease);
565
566                     if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FRESTRICTED) != 0) {
567                         // This function is not meant for the script user to use.
568                         continue;
569                     }
570
571                     ComMethodDesc method = new ComMethodDesc(typeInfo, funcDesc);
572                     string name = method.Name.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
573
574                     if ((funcDesc.invkind & ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT) != 0) {
575                         puts.Add(name, method);
576
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) {
580                             setItem = method;
581                         }
582                         continue;
583                     }
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) {
589                             setItem = method;
590                         }
591                         continue;
592                     }
593
594                     if (funcDesc.memid == ComDispIds.DISPID_NEWENUM) {
595                         funcs.Add("GETENUMERATOR", method);
596                         continue;
597                     }
598
599                     funcs.Add(name, method);
600
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) {
604                         getItem = method;
605                     }
606                 } finally {
607                     if (funcDescHandleToRelease != IntPtr.Zero) {
608                         typeInfo.ReleaseFuncDesc(funcDescHandleToRelease);
609                     }
610                 }
611             }
612
613             lock (_CacheComTypeDesc) {
614                 ComTypeDesc cachedTypeDesc;
615                 if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out cachedTypeDesc)) {
616                     _comTypeDesc = cachedTypeDesc;
617                 } else {
618                     _comTypeDesc = typeDesc;
619                     _CacheComTypeDesc.Add(typeAttr.guid, _comTypeDesc);
620                 }
621                 _comTypeDesc.Funcs = funcs;
622                 _comTypeDesc.Puts = puts;
623                 _comTypeDesc.PutRefs = putrefs;
624                 _comTypeDesc.EnsureGetItem(getItem);
625                 _comTypeDesc.EnsureSetItem(setItem);
626             }
627         }
628
629         internal bool TryGetPropertySetter(string name, out ComMethodDesc method, Type limitType, bool holdsNull) {
630             EnsureScanDefinedMethods();
631
632             if (ComBinderHelpers.PreferPut(limitType, holdsNull)) {
633                 return _comTypeDesc.TryGetPut(name, out method) ||
634                     _comTypeDesc.TryGetPutRef(name, out method);
635             } else {
636                 return _comTypeDesc.TryGetPutRef(name, out method) ||
637                     _comTypeDesc.TryGetPut(name, out method);
638             }
639         }
640     }
641 }
642
643 #endif