diff options
Diffstat (limited to 'src/mscorlib/src/System/Runtime/InteropServices/ComEventsHelper.cs')
-rw-r--r-- | src/mscorlib/src/System/Runtime/InteropServices/ComEventsHelper.cs | 220 |
1 files changed, 113 insertions, 107 deletions
diff --git a/src/mscorlib/src/System/Runtime/InteropServices/ComEventsHelper.cs b/src/mscorlib/src/System/Runtime/InteropServices/ComEventsHelper.cs index 82692c1c54..8198d9fd18 100644 --- a/src/mscorlib/src/System/Runtime/InteropServices/ComEventsHelper.cs +++ b/src/mscorlib/src/System/Runtime/InteropServices/ComEventsHelper.cs @@ -10,104 +10,105 @@ ** managed delegates to COM's connection point based events. ** **/ -namespace System.Runtime.InteropServices { - // - // #ComEventsFeature - // - // code:#ComEventsFeature defines two public methods allowing to add/remove .NET delegates handling - // events from COM objects. Those methods are defined as part of code:ComEventsHelper static class - // * code:ComEventsHelper.Combine - will create/reuse-an-existing COM event sink and register the - // specified delegate to be raised when corresponding COM event is raised - // * code:ComEventsHelper.Remove - // - // - // To bind an event handler to the COM object you need to provide the following data: - // * rcw - the instance of the COM object you want to bind to - // * iid - Guid of the source interface you want the sink to implement - // * dispid - dispatch identifier of the event on the source interface you are interested in - // * d - delegate to invoked when corresponding COM event is raised. - // - // #ComEventsArchitecture: - // In COM world, events are handled by so-called event sinks. What these are? COM-based Object Models - // (OMs) define "source" interfaces that need to be implemented by the COM clients to receive events. So, - // event sinks are COM objects implementing a source interfaces. Once an event sink is passed to the COM - // server (through a mechanism known as 'binding/advising to connection point'), COM server will be - // calling source interface methods to "fire events" (advising, connection points, firing events etc. - - // is all COM jargon). - // - // There are few interesting obervations about source interfaces. Usually source interfaces are defined - // as 'dispinterface' - meaning that only late-bound invocations on this interface are allowed. Even - // though it is not illegal to use early bound invocations on source interfaces - the practice is - // discouraged because of versioning concerns. - // - // Notice also that each COM server object might define multiple source interfaces and hence have - // multiple connection points (each CP handles exactly one source interface). COM objects that want to - // fire events are required to implement IConnectionPointContainer interface which is used by the COM - // clients to discovery connection poitns - objects implementing IConnectionPoint interface. Once - // connection point is found - clients can bind to it using IConnectionPoint::Advise (see - // code:ComEventsSink.Advise). - // - // The idea behind code:#ComEventsFeature is to write a "universal event sink" COM component that is - // generic enough to handle all late-bound event firings and invoke corresponding COM delegates (through - // reflection). - // - // When delegate is registered (using code:ComEventsHelper.Combine) we will verify we have corresponding - // event sink created and bound. - // - // But what happens when COM events are fired? code:ComEventsSink.Invoke implements IDispatch::Invoke method - // and this is the entry point that is called. Once our event sink is invoked, we need to find the - // corresponding delegate to invoke . We need to match the dispid of the call that is coming in to a - // dispid of .NET delegate that has been registered for this object. Once this is found we do call the - // delegates using reflection (code:ComEventsMethod.Invoke). - // - // #ComEventsArgsMarshalling - // Notice, that we may not have a delegate registered against every method on the source interface. If we - // were to marshal all the input parameters for methods that do not reach user code - we would end up - // generatic RCWs that are not reachable for user code (the inconvenience it might create is there will - // be RCWs that users can not call Marshal.ReleaseComObject on to explicitly manage the lifetime of these - // COM objects). The above behavior was one of the shortcoimings of legacy TLBIMP's implementation of COM - // event sinking. In our code we will not marshal any data if there is no delegate registered to handle - // the event. (code:ComEventsMethod.Invoke) - // - // #ComEventsFinalization: - // Additional area of interest is when COM sink should be unadvised from the connection point. Legacy - // TLBIMP's implementation of COM event sinks will unadvises the sink when corresponding RCW is GCed. - // This is achieved by rooting the event sinks in a finalizable object stored in RCW's property bag - // (using Marshal.SetComObjectData). Hence, once RCW is no longer reachable - the finalizer is called and - // it would unadvise all the event sinks. We are employing the same strategy here. See storing an - // instance in the RCW at code:ComEventsInfo.FromObject and undadvsing the sinks at - // code:ComEventsInfo.~ComEventsInfo - // - // Classes of interest: - // * code:ComEventsHelpers - defines public methods but there are also a number of internal classes that - // implement the actual COM event sink: - // * code:ComEventsInfo - represents a finalizable container for all event sinks for a particular RCW. - // Lifetime of this instance corresponds to the lifetime of the RCW object - // * code:ComEventsSink - represents a single event sink. Maintains an internal pointer to the next - // instance (in a singly linked list). A collection of code:ComEventsSink is stored at - // code:ComEventsInfo._sinks - // * code:ComEventsMethod - represents a single method from the source interface which has .NET delegates - // attached to it. Maintains an internal pointer to the next instance (in a singly linked list). A - // collection of code:ComEventMethod is stored at code:ComEventsSink._methods - // - // #ComEventsRetValIssue: - // Issue: normally, COM events would not return any value. However, it may happen as described in - // http://support.microsoft.com/kb/810228. Such design might represent a problem for us - e.g. what is - // the return value of a chain of delegates - is it the value of the last call in the chain or the the - // first one? As the above KB article indicates, in cases where OM has events returning values, it is - // suggested that people implement their event sink by explicitly implementing the source interface. This - // means that the problem is already quite complex and we should not be dealing with it - see - // code:ComEventsMethod.Invoke - - using System; - using System.Runtime.Remoting; - +// +// #ComEventsFeature +// +// code:#ComEventsFeature defines two public methods allowing to add/remove .NET delegates handling +// events from COM objects. Those methods are defined as part of code:ComEventsHelper static class +// * code:ComEventsHelper.Combine - will create/reuse-an-existing COM event sink and register the +// specified delegate to be raised when corresponding COM event is raised +// * code:ComEventsHelper.Remove +// +// +// To bind an event handler to the COM object you need to provide the following data: +// * rcw - the instance of the COM object you want to bind to +// * iid - Guid of the source interface you want the sink to implement +// * dispid - dispatch identifier of the event on the source interface you are interested in +// * d - delegate to invoked when corresponding COM event is raised. +// +// #ComEventsArchitecture: +// In COM world, events are handled by so-called event sinks. What these are? COM-based Object Models +// (OMs) define "source" interfaces that need to be implemented by the COM clients to receive events. So, +// event sinks are COM objects implementing a source interfaces. Once an event sink is passed to the COM +// server (through a mechanism known as 'binding/advising to connection point'), COM server will be +// calling source interface methods to "fire events" (advising, connection points, firing events etc. - +// is all COM jargon). +// +// There are few interesting obervations about source interfaces. Usually source interfaces are defined +// as 'dispinterface' - meaning that only late-bound invocations on this interface are allowed. Even +// though it is not illegal to use early bound invocations on source interfaces - the practice is +// discouraged because of versioning concerns. +// +// Notice also that each COM server object might define multiple source interfaces and hence have +// multiple connection points (each CP handles exactly one source interface). COM objects that want to +// fire events are required to implement IConnectionPointContainer interface which is used by the COM +// clients to discovery connection poitns - objects implementing IConnectionPoint interface. Once +// connection point is found - clients can bind to it using IConnectionPoint::Advise (see +// code:ComEventsSink.Advise). +// +// The idea behind code:#ComEventsFeature is to write a "universal event sink" COM component that is +// generic enough to handle all late-bound event firings and invoke corresponding COM delegates (through +// reflection). +// +// When delegate is registered (using code:ComEventsHelper.Combine) we will verify we have corresponding +// event sink created and bound. +// +// But what happens when COM events are fired? code:ComEventsSink.Invoke implements IDispatch::Invoke method +// and this is the entry point that is called. Once our event sink is invoked, we need to find the +// corresponding delegate to invoke . We need to match the dispid of the call that is coming in to a +// dispid of .NET delegate that has been registered for this object. Once this is found we do call the +// delegates using reflection (code:ComEventsMethod.Invoke). +// +// #ComEventsArgsMarshalling +// Notice, that we may not have a delegate registered against every method on the source interface. If we +// were to marshal all the input parameters for methods that do not reach user code - we would end up +// generatic RCWs that are not reachable for user code (the inconvenience it might create is there will +// be RCWs that users can not call Marshal.ReleaseComObject on to explicitly manage the lifetime of these +// COM objects). The above behavior was one of the shortcoimings of legacy TLBIMP's implementation of COM +// event sinking. In our code we will not marshal any data if there is no delegate registered to handle +// the event. (code:ComEventsMethod.Invoke) +// +// #ComEventsFinalization: +// Additional area of interest is when COM sink should be unadvised from the connection point. Legacy +// TLBIMP's implementation of COM event sinks will unadvises the sink when corresponding RCW is GCed. +// This is achieved by rooting the event sinks in a finalizable object stored in RCW's property bag +// (using Marshal.SetComObjectData). Hence, once RCW is no longer reachable - the finalizer is called and +// it would unadvise all the event sinks. We are employing the same strategy here. See storing an +// instance in the RCW at code:ComEventsInfo.FromObject and undadvsing the sinks at +// code:ComEventsInfo.~ComEventsInfo +// +// Classes of interest: +// * code:ComEventsHelpers - defines public methods but there are also a number of internal classes that +// implement the actual COM event sink: +// * code:ComEventsInfo - represents a finalizable container for all event sinks for a particular RCW. +// Lifetime of this instance corresponds to the lifetime of the RCW object +// * code:ComEventsSink - represents a single event sink. Maintains an internal pointer to the next +// instance (in a singly linked list). A collection of code:ComEventsSink is stored at +// code:ComEventsInfo._sinks +// * code:ComEventsMethod - represents a single method from the source interface which has .NET delegates +// attached to it. Maintains an internal pointer to the next instance (in a singly linked list). A +// collection of code:ComEventMethod is stored at code:ComEventsSink._methods +// +// #ComEventsRetValIssue: +// Issue: normally, COM events would not return any value. However, it may happen as described in +// http://support.microsoft.com/kb/810228. Such design might represent a problem for us - e.g. what is +// the return value of a chain of delegates - is it the value of the last call in the chain or the the +// first one? As the above KB article indicates, in cases where OM has events returning values, it is +// suggested that people implement their event sink by explicitly implementing the source interface. This +// means that the problem is already quite complex and we should not be dealing with it - see +// code:ComEventsMethod.Invoke + +using System; +using System.Runtime.Remoting; + +namespace System.Runtime.InteropServices +{ /// <summary> /// The static methods provided in ComEventsHelper allow using .NET delegates to subscribe to events /// raised COM objects. /// </summary> - public static class ComEventsHelper { - + public static class ComEventsHelper + { /// <summary> /// Adds a delegate to the invocation list of events originating from the COM object. /// </summary> @@ -115,21 +116,24 @@ namespace System.Runtime.InteropServices { /// <param name="iid">identifier of the source interface used by COM object to fire events</param> /// <param name="dispid">dispatch identifier of the method on the source interface</param> /// <param name="d">delegate to invoke when specifed COM event is fired</param> - public static void Combine(object rcw, Guid iid, int dispid, System.Delegate d) { - + public static void Combine(object rcw, Guid iid, int dispid, System.Delegate d) + { rcw = UnwrapIfTransparentProxy(rcw); - lock (rcw) { + lock (rcw) + { ComEventsInfo eventsInfo = ComEventsInfo.FromObject(rcw); ComEventsSink sink = eventsInfo.FindSink(ref iid); - if (sink == null) { + if (sink == null) + { sink = eventsInfo.AddSink(ref iid); } ComEventsMethod method = sink.FindMethod(dispid); - if (method == null) { + if (method == null) + { method = sink.AddMethod(dispid); } @@ -145,12 +149,12 @@ namespace System.Runtime.InteropServices { /// <param name="dispid">dispatch identifier of the method on the source interface</param> /// <param name="d">delegate to remove from the invocation list</param> /// <returns></returns> - public static Delegate Remove(object rcw, Guid iid, int dispid, System.Delegate d) { - + public static Delegate Remove(object rcw, Guid iid, int dispid, System.Delegate d) + { rcw = UnwrapIfTransparentProxy(rcw); - lock (rcw) { - + lock (rcw) + { ComEventsInfo eventsInfo = ComEventsInfo.Find(rcw); if (eventsInfo == null) return null; @@ -163,15 +167,18 @@ namespace System.Runtime.InteropServices { method.RemoveDelegate(d); - if (method.Empty) { + if (method.Empty) + { // removed the last event handler for this dispid - need to remove dispid handler method = sink.RemoveMethod(method); } - if (method == null) { + if (method == null) + { // removed last dispid handler for this sink - need to remove the sink sink = eventsInfo.RemoveSink(sink); } - if (sink == null) { + if (sink == null) + { // removed last sink for this rcw - need to remove all traces of event info Marshal.SetComObjectData(rcw, typeof(ComEventsInfo), null); GC.SuppressFinalize(eventsInfo); @@ -186,5 +193,4 @@ namespace System.Runtime.InteropServices { return rcw; } } - } |