PageRenderTime 205ms CodeModel.GetById 121ms app.highlight 14ms RepoModel.GetById 66ms app.codeStats 0ms

/Microsoft.Dynamic/ComEventSink.cs

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