PageRenderTime 51ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/Runtime/Microsoft.Dynamic/ComInterop/ComEventSink.cs

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