/* ****************************************************************************
*
* Copyright (c) Microsoft Corporation.
*
* This source code is subject to terms and conditions of the Microsoft Public License. A
* copy of the license can be found in the License.html file at the root of this distribution. If
* you cannot locate the Microsoft Public License, please send an email to
* dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
* by the terms of the Microsoft Public License.
*
* You must not remove this notice, or any other, from this software.
*
*
* ***************************************************************************/
#if !SILVERLIGHT // ComObject
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using ComTypesSystem.Runtime.InteropServices.ComTypes;
using Microsoft.Scripting.Utils;
namespace Microsoft.Scripting.ComInterop{
/// <summary>
/// This class implements an event sink for a particular RCW.
/// Unlike the implementation of events in TlbImp'd assemblies,
/// we will create only one event sink per RCW (theoretically RCW might have
/// several ComEventSink evenk sinks - but all these implement different source intefaces).
/// Each ComEventSink contains a list of ComEventSinkMethod objects - which represent
/// a single method on the source interface an a multicast delegate to redirect
/// the calls. Notice that we are chaining multicast delegates so that same
/// ComEventSinkMedhod can invoke multiple event handlers).
///
/// ComEventSink implements an IDisposable pattern to Unadvise from the connection point.
/// Typically, when RCW is finalized the corresponding Dispose will be triggered by
/// ComEventSinksContainer finalizer. Notice that lifetime of ComEventSinksContainer
/// is bound to the lifetime of the RCW.
/// </summary>
internal sealed class ComEventSink : MarshalByRefObject, IReflect, IDisposable {
#region private fields
private Guid _sourceIid;
private ComTypes.IConnectionPoint _connectionPoint;
private int _adviseCookie;
private List<ComEventSinkMethod> _comEventSinkMethods;
private object _lockObject = new object(); // We cannot lock on ComEventSink since it causes a DoNotLockOnObjectsWithWeakIdentity warning
#endregion
#region private classes
/// <summary>
/// Contains a methods DISPID (in a string formatted of "[DISPID=N]"
/// and a chained list of delegates to invoke
/// </summary>
private class ComEventSinkMethod {
public string _name;
public Func<object[], object> _handlers;
}
#endregion
#region ctor
private ComEventSink(object rcw, Guid sourceIid) {
Initialize(rcw, sourceIid);
}
#endregion
private void Initialize(object rcw, Guid sourceIid) {
_sourceIid = sourceIid;
_adviseCookie = -1;
Debug.Assert(_connectionPoint == null, "re-initializing event sink w/o unadvising from connection point");
ComTypes.IConnectionPointContainer cpc = rcw as ComTypes.IConnectionPointContainer;
if (cpc == null)
throw Error.COMObjectDoesNotSupportEvents();
cpc.FindConnectionPoint(ref _sourceIid, out _connectionPoint);
if (_connectionPoint == null)
throw Error.COMObjectDoesNotSupportSourceInterface();
// Read the comments for ComEventSinkProxy about why we need it
ComEventSinkProxy proxy = new ComEventSinkProxy(this, _sourceIid);
_connectionPoint.Advise(proxy.GetTransparentProxy(), out _adviseCookie);
}
#region static methods
public static ComEventSink FromRuntimeCallableWrapper(object rcw, Guid sourceIid, bool createIfNotFound) {
List<ComEventSink> comEventSinks = ComEventSinksContainer.FromRuntimeCallableWrapper(rcw, createIfNotFound);
if (comEventSinks == null) {
return null;
}
ComEventSink comEventSink = null;
lock (comEventSinks) {
foreach (ComEventSink sink in comEventSinks) {
if (sink._sourceIid == sourceIid) {
comEventSink = sink;
break;
} else if (sink._sourceIid == Guid.Empty) {
// we found a ComEventSink object that
// was previously disposed. Now we will reuse it.
sink.Initialize(rcw, sourceIid);
comEventSink = sink;
}
}
if (comEventSink == null && createIfNotFound == true) {
comEventSink = new ComEventSink(rcw, sourceIid);
comEventSinks.Add(comEventSink);
}
}
return comEventSink;
}
#endregion
public void AddHandler(int dispid, object func) {
string name = String.Format(CultureInfo.InvariantCulture, "[DISPID={0}]", dispid);
lock (_lockObject) {
ComEventSinkMethod sinkMethod;
sinkMethod = FindSinkMethod(name);
if (sinkMethod == null) {
if (_comEventSinkMethods == null) {
_comEventSinkMethods = new List<ComEventSinkMethod>();
}
sinkMethod = new ComEventSinkMethod();
sinkMethod._name = name;
_comEventSinkMethods.Add(sinkMethod);
}
sinkMethod._handlers += new SplatCallSite(func).Invoke;
}
}
public void RemoveHandler(int dispid, object func) {
string name = String.Format(CultureInfo.InvariantCulture, "[DISPID={0}]", dispid);
lock (_lockObject) {
ComEventSinkMethod sinkEntry = FindSinkMethod(name);
if (sinkEntry == null){
return;
}
// Remove the delegate from multicast delegate chain.
// We will need to find the delegate that corresponds
// to the func handler we want to remove. This will be
// easy since we Target property of the delegate object
// is a ComEventCallContext object.
Delegate[] delegates = sinkEntry._handlers.GetInvocationList();
foreach (Delegate d in delegates) {
SplatCallSite callContext = d.Target as SplatCallSite;
if (callContext != null && callContext._callable.Equals(func)) {
sinkEntry._handlers -= d as Func<object[], object>;
break;
}
}
// If the delegates chain is empty - we can remove
// corresponding ComEvenSinkEntry
if (sinkEntry._handlers == null)
_comEventSinkMethods.Remove(sinkEntry);
// We can Unadvise from the ConnectionPoint if no more sink entries
// are registered for this interface
//(calling Dispose will call IConnectionPoint.Unadvise).
if (_comEventSinkMethods.Count == 0) {
// notice that we do not remove
// ComEventSinkEntry from the list, we will re-use this data structure
// if a new handler needs to be attached.
Dispose();
}
}
}
public object ExecuteHandler(string name, object[] args) {
ComEventSinkMethod site;
site = FindSinkMethod(name);
if (site != null && site._handlers != null) {
return site._handlers(args);
}
return null;
}
#region IReflect
#region Unimplemented members
public FieldInfo GetField(string name, BindingFlags bindingAttr) {
return null;
}
public FieldInfo[] GetFields(BindingFlags bindingAttr) {
return new FieldInfo[0];
}
public MemberInfo[] GetMember(string name, BindingFlags bindingAttr) {
return new MemberInfo[0];
}
public MemberInfo[] GetMembers(BindingFlags bindingAttr) {
return new MemberInfo[0];
}
public MethodInfo GetMethod(string name, BindingFlags bindingAttr) {
return null;
}
public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) {
return null;
}
public MethodInfo[] GetMethods(BindingFlags bindingAttr) {
return new MethodInfo[0];
}
public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) {
return null;
}
public PropertyInfo GetProperty(string name, BindingFlags bindingAttr) {
return null;
}
public PropertyInfo[] GetProperties(BindingFlags bindingAttr) {
return new PropertyInfo[0];
}
#endregion
public Type UnderlyingSystemType {
get {
return typeof(object);
}
}
public object InvokeMember(
string name,
BindingFlags invokeAttr,
Binder binder,
object target,
object[] args,
ParameterModifier[] modifiers,
CultureInfo culture,
string[] namedParameters) {
return ExecuteHandler(name, args);
}
#endregion
#region IDisposable
public void Dispose() {
DisposeAll();
GC.SuppressFinalize(this);
}
#endregion
~ComEventSink() {
DisposeAll();
}
private void DisposeAll() {
if (_connectionPoint == null) {
return;
}
if (_adviseCookie == -1) {
return;
}
try {
_connectionPoint.Unadvise(_adviseCookie);
// _connectionPoint has entered the CLR in the constructor
// for this object and hence its ref counter has been increased
// by us. We have not exposed it to other components and
// hence it is safe to call RCO on it w/o worrying about
// killing the RCW for other objects that link to it.
Marshal.ReleaseComObject(_connectionPoint);
} catch (Exception ex) {
// if something has gone wrong, and the object is no longer attached to the CLR,
// the Unadvise is going to throw. In this case, since we're going away anyway,
// we'll ignore the failure and quietly go on our merry way.
COMException exCOM = ex as COMException;
if (exCOM != null && exCOM.ErrorCode == ComHresults.CONNECT_E_NOCONNECTION) {
Debug.Assert(false, "IConnectionPoint::Unadvise returned CONNECT_E_NOCONNECTION.");
throw;
}
} finally {
_connectionPoint = null;
_adviseCookie = -1;
_sourceIid = Guid.Empty;
}
}
private ComEventSinkMethod FindSinkMethod(string name) {
if (_comEventSinkMethods == null)
return null;
ComEventSinkMethod site;
site = _comEventSinkMethods.Find(element => element._name == name);
return site;
}
}
}
#endif
|