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.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;
29 namespace System.Dynamic {
31 namespace Microsoft.Scripting {
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).
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.
48 internal sealed class ComEventSink : MarshalByRefObject, IReflect, IDisposable {
50 #region private fields
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
60 #region private classes
63 /// Contains a methods DISPID (in a string formatted of "[DISPID=N]"
64 /// and a chained list of delegates to invoke
66 private class ComEventSinkMethod {
68 public Func<object[], object> _handlers;
75 private ComEventSink(object rcw, Guid sourceIid) {
76 Initialize(rcw, sourceIid);
82 private void Initialize(object rcw, Guid sourceIid) {
83 _sourceIid = sourceIid;
86 Debug.Assert(_connectionPoint == null, "re-initializing event sink w/o unadvising from connection point");
88 ComTypes.IConnectionPointContainer cpc = rcw as ComTypes.IConnectionPointContainer;
90 throw Error.COMObjectDoesNotSupportEvents();
92 cpc.FindConnectionPoint(ref _sourceIid, out _connectionPoint);
93 if (_connectionPoint == null)
94 throw Error.COMObjectDoesNotSupportSourceInterface();
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);
101 #region static methods
104 public static ComEventSink FromRuntimeCallableWrapper(object rcw, Guid sourceIid, bool createIfNotFound) {
105 List<ComEventSink> comEventSinks = ComEventSinksContainer.FromRuntimeCallableWrapper(rcw, createIfNotFound);
107 if (comEventSinks == null) {
111 ComEventSink comEventSink = null;
112 lock (comEventSinks) {
114 foreach (ComEventSink sink in comEventSinks) {
115 if (sink._sourceIid == sourceIid) {
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);
126 if (comEventSink == null && createIfNotFound == true) {
127 comEventSink = new ComEventSink(rcw, sourceIid);
128 comEventSinks.Add(comEventSink);
137 public void AddHandler(int dispid, object func) {
138 string name = String.Format(CultureInfo.InvariantCulture, "[DISPID={0}]", dispid);
141 ComEventSinkMethod sinkMethod;
142 sinkMethod = FindSinkMethod(name);
144 if (sinkMethod == null) {
145 if (_comEventSinkMethods == null) {
146 _comEventSinkMethods = new List<ComEventSinkMethod>();
149 sinkMethod = new ComEventSinkMethod();
150 sinkMethod._name = name;
151 _comEventSinkMethods.Add(sinkMethod);
154 sinkMethod._handlers += new SplatCallSite(func).Invoke;
159 public void RemoveHandler(int dispid, object func) {
161 string name = String.Format(CultureInfo.InvariantCulture, "[DISPID={0}]", dispid);
165 ComEventSinkMethod sinkEntry = FindSinkMethod(name);
166 if (sinkEntry == null){
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>;
184 // If the delegates chain is empty - we can remove
185 // corresponding ComEvenSinkEntry
186 if (sinkEntry._handlers == null)
187 _comEventSinkMethods.Remove(sinkEntry);
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.
201 public object ExecuteHandler(string name, object[] args) {
202 ComEventSinkMethod site;
203 site = FindSinkMethod(name);
205 if (site != null && site._handlers != null) {
206 return site._handlers(args);
214 #region Unimplemented members
216 public FieldInfo GetField(string name, BindingFlags bindingAttr) {
220 public FieldInfo[] GetFields(BindingFlags bindingAttr) {
221 return new FieldInfo[0];
224 public MemberInfo[] GetMember(string name, BindingFlags bindingAttr) {
225 return new MemberInfo[0];
228 public MemberInfo[] GetMembers(BindingFlags bindingAttr) {
229 return new MemberInfo[0];
232 public MethodInfo GetMethod(string name, BindingFlags bindingAttr) {
236 public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) {
240 public MethodInfo[] GetMethods(BindingFlags bindingAttr) {
241 return new MethodInfo[0];
244 public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) {
248 public PropertyInfo GetProperty(string name, BindingFlags bindingAttr) {
252 public PropertyInfo[] GetProperties(BindingFlags bindingAttr) {
253 return new PropertyInfo[0];
258 public Type UnderlyingSystemType {
260 return typeof(object);
264 public object InvokeMember(
266 BindingFlags invokeAttr,
270 ParameterModifier[] modifiers,
272 string[] namedParameters) {
274 return ExecuteHandler(name, args);
281 #if MICROSOFT_DYNAMIC
282 [SecurityCritical, SecurityTreatAsSafe]
284 [SecuritySafeCritical]
286 public void Dispose() {
288 GC.SuppressFinalize(this);
293 #if MICROSOFT_DYNAMIC
294 [SecurityCritical, SecurityTreatAsSafe]
296 [SecuritySafeCritical]
303 private void DisposeAll() {
304 if (_connectionPoint == null) {
308 if (_adviseCookie == -1) {
313 _connectionPoint.Unadvise(_adviseCookie);
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.");
331 _connectionPoint = null;
333 _sourceIid = Guid.Empty;
337 private ComEventSinkMethod FindSinkMethod(string name) {
338 if (_comEventSinkMethods == null)
341 ComEventSinkMethod site;
342 site = _comEventSinkMethods.Find(element => element._name == name);