2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Dynamic / ComEventSink.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.Generic;
21 using System.Diagnostics;
22 using System.Globalization;
23 using System.Reflection;
24 using System.Runtime.InteropServices;
25 using System.Security;
26 using ComTypes = System.Runtime.InteropServices.ComTypes;
27
28 #if CODEPLEX_40
29 namespace System.Dynamic {
30 #else
31 namespace Microsoft.Scripting {
32 #endif
33     /// <summary>
34     /// This class implements an event sink for a particular RCW.
35     /// Unlike the implementation of events in TlbImp'd assemblies,
36     /// we will create only one event sink per RCW (theoretically RCW might have
37     /// several ComEventSink evenk sinks - but all these implement different source intefaces).
38     /// Each ComEventSink contains a list of ComEventSinkMethod objects - which represent
39     /// a single method on the source interface an a multicast delegate to redirect 
40     /// the calls. Notice that we are chaining multicast delegates so that same 
41     /// ComEventSinkMedhod can invoke multiple event handlers).
42     /// 
43     /// ComEventSink implements an IDisposable pattern to Unadvise from the connection point.
44     /// Typically, when RCW is finalized the corresponding Dispose will be triggered by 
45     /// ComEventSinksContainer finalizer. Notice that lifetime of ComEventSinksContainer
46     /// is bound to the lifetime of the RCW. 
47     /// </summary>
48     internal sealed class ComEventSink : MarshalByRefObject, IReflect, IDisposable {
49
50         #region private fields
51
52         private Guid _sourceIid;
53         private ComTypes.IConnectionPoint _connectionPoint;
54         private int _adviseCookie;
55         private List<ComEventSinkMethod> _comEventSinkMethods;
56         private object _lockObject = new object(); // We cannot lock on ComEventSink since it causes a DoNotLockOnObjectsWithWeakIdentity warning
57
58         #endregion
59
60         #region private classes
61
62         /// <summary>
63         /// Contains a methods DISPID (in a string formatted of "[DISPID=N]"
64         /// and a chained list of delegates to invoke
65         /// </summary>
66         private class ComEventSinkMethod {
67             public string _name;
68             public Func<object[], object> _handlers;
69         }
70         #endregion
71
72         #region ctor
73
74         [SecurityCritical]
75         private ComEventSink(object rcw, Guid sourceIid) {
76             Initialize(rcw, sourceIid);
77         }
78
79         #endregion
80
81         [SecurityCritical]
82         private void Initialize(object rcw, Guid sourceIid) {
83             _sourceIid = sourceIid;
84             _adviseCookie = -1;
85
86             Debug.Assert(_connectionPoint == null, "re-initializing event sink w/o unadvising from connection point");
87
88             ComTypes.IConnectionPointContainer cpc = rcw as ComTypes.IConnectionPointContainer;
89             if (cpc == null)
90                 throw Error.COMObjectDoesNotSupportEvents();
91
92             cpc.FindConnectionPoint(ref _sourceIid, out _connectionPoint);
93             if (_connectionPoint == null)
94                 throw Error.COMObjectDoesNotSupportSourceInterface();
95
96             // Read the comments for ComEventSinkProxy about why we need it
97             ComEventSinkProxy proxy = new ComEventSinkProxy(this, _sourceIid);
98             _connectionPoint.Advise(proxy.GetTransparentProxy(), out _adviseCookie);
99         }
100
101         #region static methods
102
103         [SecurityCritical]
104         public static ComEventSink FromRuntimeCallableWrapper(object rcw, Guid sourceIid, bool createIfNotFound) {
105             List<ComEventSink> comEventSinks = ComEventSinksContainer.FromRuntimeCallableWrapper(rcw, createIfNotFound);
106
107             if (comEventSinks == null) {
108                 return null;
109             }
110
111             ComEventSink comEventSink = null;
112             lock (comEventSinks) {
113
114                 foreach (ComEventSink sink in comEventSinks) {
115                     if (sink._sourceIid == sourceIid) {
116                         comEventSink = sink;
117                         break;
118                     } else if (sink._sourceIid == Guid.Empty) {
119                         // we found a ComEventSink object that 
120                         // was previously disposed. Now we will reuse it.
121                         sink.Initialize(rcw, sourceIid);
122                         comEventSink = sink;
123                     }
124                 }
125
126                 if (comEventSink == null && createIfNotFound == true) {
127                     comEventSink = new ComEventSink(rcw, sourceIid);
128                     comEventSinks.Add(comEventSink);
129                 }
130             }
131
132             return comEventSink;
133         }
134
135         #endregion
136
137         public void AddHandler(int dispid, object func) {
138             string name = String.Format(CultureInfo.InvariantCulture, "[DISPID={0}]", dispid);
139
140             lock (_lockObject) {
141                 ComEventSinkMethod sinkMethod;
142                 sinkMethod = FindSinkMethod(name);
143
144                 if (sinkMethod == null) {
145                     if (_comEventSinkMethods == null) {
146                         _comEventSinkMethods = new List<ComEventSinkMethod>();
147                     }
148
149                     sinkMethod = new ComEventSinkMethod();
150                     sinkMethod._name = name;
151                     _comEventSinkMethods.Add(sinkMethod);
152                 }
153
154                 sinkMethod._handlers += new SplatCallSite(func).Invoke;
155             }
156         }
157
158         [SecurityCritical]
159         public void RemoveHandler(int dispid, object func) {
160
161             string name = String.Format(CultureInfo.InvariantCulture, "[DISPID={0}]", dispid);
162
163             lock (_lockObject) {
164
165                 ComEventSinkMethod sinkEntry = FindSinkMethod(name);
166                 if (sinkEntry == null){
167                     return;
168                 }
169
170                 // Remove the delegate from multicast delegate chain.
171                 // We will need to find the delegate that corresponds
172                 // to the func handler we want to remove. This will be
173                 // easy since we Target property of the delegate object
174                 // is a ComEventCallContext object.
175                 Delegate[] delegates = sinkEntry._handlers.GetInvocationList();
176                 foreach (Delegate d in delegates) {
177                     SplatCallSite callContext = d.Target as SplatCallSite;
178                     if (callContext != null && callContext._callable.Equals(func)) {
179                         sinkEntry._handlers -= d as Func<object[], object>;
180                         break;
181                     }
182                 }
183
184                 // If the delegates chain is empty - we can remove 
185                 // corresponding ComEvenSinkEntry
186                 if (sinkEntry._handlers == null)
187                     _comEventSinkMethods.Remove(sinkEntry);
188
189                 // We can Unadvise from the ConnectionPoint if no more sink entries
190                 // are registered for this interface 
191                 //(calling Dispose will call IConnectionPoint.Unadvise).
192                 if (_comEventSinkMethods.Count == 0) {
193                     // notice that we do not remove 
194                     // ComEventSinkEntry from the list, we will re-use this data structure
195                     // if a new handler needs to be attached.
196                     Dispose();
197                 }
198             }
199         }
200
201         public object ExecuteHandler(string name, object[] args) {
202             ComEventSinkMethod site;
203             site = FindSinkMethod(name);
204
205             if (site != null && site._handlers != null) {
206                 return site._handlers(args);
207             }
208
209             return null;
210         }
211
212         #region IReflect
213
214         #region Unimplemented members
215
216         public FieldInfo GetField(string name, BindingFlags bindingAttr) {
217             return null;
218         }
219
220         public FieldInfo[] GetFields(BindingFlags bindingAttr) {
221             return new FieldInfo[0];
222         }
223
224         public MemberInfo[] GetMember(string name, BindingFlags bindingAttr) {
225             return new MemberInfo[0];
226         }
227
228         public MemberInfo[] GetMembers(BindingFlags bindingAttr) {
229             return new MemberInfo[0];
230         }
231
232         public MethodInfo GetMethod(string name, BindingFlags bindingAttr) {
233             return null;
234         }
235
236         public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) {
237             return null;
238         }
239
240         public MethodInfo[] GetMethods(BindingFlags bindingAttr) {
241             return new MethodInfo[0];
242         }
243
244         public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) {
245             return null;
246         }
247
248         public PropertyInfo GetProperty(string name, BindingFlags bindingAttr) {
249             return null;
250         }
251
252         public PropertyInfo[] GetProperties(BindingFlags bindingAttr) {
253             return new PropertyInfo[0];
254         }
255
256         #endregion
257
258         public Type UnderlyingSystemType {
259             get {
260                 return typeof(object);
261             }
262         }
263
264         public object InvokeMember(
265             string name,
266             BindingFlags invokeAttr,
267             Binder binder,
268             object target,
269             object[] args,
270             ParameterModifier[] modifiers,
271             CultureInfo culture,
272             string[] namedParameters) {
273
274             return ExecuteHandler(name, args);
275         }
276
277         #endregion
278
279         #region IDisposable
280
281 #if MICROSOFT_DYNAMIC
282         [SecurityCritical, SecurityTreatAsSafe]
283 #else
284         [SecuritySafeCritical]
285 #endif
286         public void Dispose() {
287             DisposeAll();
288             GC.SuppressFinalize(this);
289         }
290
291         #endregion
292
293 #if MICROSOFT_DYNAMIC
294         [SecurityCritical, SecurityTreatAsSafe]
295 #else
296         [SecuritySafeCritical]
297 #endif
298         ~ComEventSink() {
299             DisposeAll();
300         }
301
302         [SecurityCritical]
303         private void DisposeAll() {
304             if (_connectionPoint == null) {
305                 return;
306             }
307
308             if (_adviseCookie == -1) {
309                 return;
310             }
311
312             try {
313                 _connectionPoint.Unadvise(_adviseCookie);
314
315                 // _connectionPoint has entered the CLR in the constructor
316                 // for this object and hence its ref counter has been increased
317                 // by us. We have not exposed it to other components and
318                 // hence it is safe to call RCO on it w/o worrying about
319                 // killing the RCW for other objects that link to it.
320                 Marshal.ReleaseComObject(_connectionPoint);
321             } catch (Exception ex) {
322                 // if something has gone wrong, and the object is no longer attached to the CLR,
323                 // the Unadvise is going to throw.  In this case, since we're going away anyway,
324                 // we'll ignore the failure and quietly go on our merry way.
325                 COMException exCOM = ex as COMException;
326                 if (exCOM != null && exCOM.ErrorCode == ComHresults.CONNECT_E_NOCONNECTION) {
327                     Debug.Assert(false, "IConnectionPoint::Unadvise returned CONNECT_E_NOCONNECTION.");
328                     throw;
329                 }
330             } finally {
331                 _connectionPoint = null;
332                 _adviseCookie = -1;
333                 _sourceIid = Guid.Empty;
334             }
335         }
336
337         private ComEventSinkMethod FindSinkMethod(string name) {
338             if (_comEventSinkMethods == null)
339                 return null;
340
341             ComEventSinkMethod site;
342             site = _comEventSinkMethods.Find(element => element._name == name);
343
344             return site;
345         }
346     }
347 }
348
349 #endif