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