ComEventsHelper.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / clr / src / BCL / System / Runtime / InteropServices / ComEventsHelper.cs / 1305376 / ComEventsHelper.cs

                            // ==++== 
//
//   Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--== 

/*============================================================ 
** 
** Class:  ComEventsHelper
** 
** Purpose: ComEventHelpers APIs allow binding
** managed delegates to COM's connection point based events.
**
** Date:  April 2008 
**/
 
#if FEATURE_COMINTEROP 

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; 

    /// 
    /// The static methods provided in ComEventsHelper allow using .NET delegates to subscribe to events
    /// raised COM objects. 
    /// 
    public static class ComEventsHelper { 
 
        /// 
        /// Adds a delegate to the invocation list of events originating from the COM object. 
        /// 
        /// COM object firing the events the caller would like to respond to
        /// identifier of the source interface used by COM object to fire events
        /// dispatch identifier of the method on the source interface 
        /// delegate to invoke when specifed COM event is fired
        [System.Security.SecurityCritical] 
        public static void Combine(object rcw, Guid iid, int dispid, System.Delegate d) { 

            rcw = UnwrapIfTransparentProxy(rcw); 

            lock (rcw) {
                ComEventsInfo eventsInfo = ComEventsInfo.FromObject(rcw);
 
                ComEventsSink sink = eventsInfo.FindSink(ref iid);
                if (sink == null) { 
                    sink = eventsInfo.AddSink(ref iid); 
                }
 

                ComEventsMethod method = sink.FindMethod(dispid);
                if (method == null) {
                    method = sink.AddMethod(dispid); 
                }
 
                method.AddDelegate(d); 
            }
        } 

        /// 
        /// Removes a delegate from the invocation list of events originating from the COM object.
        ///  
        /// COM object the delegate is attached to
        /// identifier of the source interface used by COM object to fire events 
        /// dispatch identifier of the method on the source interface 
        /// delegate to remove from the invocation list
        ///  
        [System.Security.SecurityCritical]
        public static Delegate Remove(object rcw, Guid iid, int dispid, System.Delegate d) {

            rcw = UnwrapIfTransparentProxy(rcw); 

            lock (rcw) { 
 
                ComEventsInfo eventsInfo = ComEventsInfo.Find(rcw);
                if (eventsInfo == null) 
                    return null;
                ComEventsSink sink = eventsInfo.FindSink(ref iid);
                if (sink == null)
                    return null; 
                ComEventsMethod method = sink.FindMethod(dispid);
                if (method == null) 
                    return null; 

                method.RemoveDelegate(d); 

                if (method.Empty) {
                    // removed the last event handler for this dispid - need to remove dispid handler
                    method = sink.RemoveMethod(method); 
                }
                if (method == null) { 
                    // removed last dispid handler for this sink - need to remove the sink 
                    sink = eventsInfo.RemoveSink(sink);
                } 
                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); 
                }
 
                return d; 
            }
        } 

        [System.Security.SecurityCritical]
        internal static object UnwrapIfTransparentProxy(object rcw) {
            if (RemotingServices.IsTransparentProxy(rcw)) { 
                IntPtr punk = Marshal.GetIUnknownForObject(rcw);
                try { 
                    rcw = Marshal.GetObjectForIUnknown(punk); 
                } finally {
                    Marshal.Release(punk); 
                }
            }

            return rcw; 
        }
    } 
 
}
 
#endif

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// ==++== 
//
//   Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--== 

/*============================================================ 
** 
** Class:  ComEventsHelper
** 
** Purpose: ComEventHelpers APIs allow binding
** managed delegates to COM's connection point based events.
**
** Date:  April 2008 
**/
 
#if FEATURE_COMINTEROP 

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; 

    /// 
    /// The static methods provided in ComEventsHelper allow using .NET delegates to subscribe to events
    /// raised COM objects. 
    /// 
    public static class ComEventsHelper { 
 
        /// 
        /// Adds a delegate to the invocation list of events originating from the COM object. 
        /// 
        /// COM object firing the events the caller would like to respond to
        /// identifier of the source interface used by COM object to fire events
        /// dispatch identifier of the method on the source interface 
        /// delegate to invoke when specifed COM event is fired
        [System.Security.SecurityCritical] 
        public static void Combine(object rcw, Guid iid, int dispid, System.Delegate d) { 

            rcw = UnwrapIfTransparentProxy(rcw); 

            lock (rcw) {
                ComEventsInfo eventsInfo = ComEventsInfo.FromObject(rcw);
 
                ComEventsSink sink = eventsInfo.FindSink(ref iid);
                if (sink == null) { 
                    sink = eventsInfo.AddSink(ref iid); 
                }
 

                ComEventsMethod method = sink.FindMethod(dispid);
                if (method == null) {
                    method = sink.AddMethod(dispid); 
                }
 
                method.AddDelegate(d); 
            }
        } 

        /// 
        /// Removes a delegate from the invocation list of events originating from the COM object.
        ///  
        /// COM object the delegate is attached to
        /// identifier of the source interface used by COM object to fire events 
        /// dispatch identifier of the method on the source interface 
        /// delegate to remove from the invocation list
        ///  
        [System.Security.SecurityCritical]
        public static Delegate Remove(object rcw, Guid iid, int dispid, System.Delegate d) {

            rcw = UnwrapIfTransparentProxy(rcw); 

            lock (rcw) { 
 
                ComEventsInfo eventsInfo = ComEventsInfo.Find(rcw);
                if (eventsInfo == null) 
                    return null;
                ComEventsSink sink = eventsInfo.FindSink(ref iid);
                if (sink == null)
                    return null; 
                ComEventsMethod method = sink.FindMethod(dispid);
                if (method == null) 
                    return null; 

                method.RemoveDelegate(d); 

                if (method.Empty) {
                    // removed the last event handler for this dispid - need to remove dispid handler
                    method = sink.RemoveMethod(method); 
                }
                if (method == null) { 
                    // removed last dispid handler for this sink - need to remove the sink 
                    sink = eventsInfo.RemoveSink(sink);
                } 
                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); 
                }
 
                return d; 
            }
        } 

        [System.Security.SecurityCritical]
        internal static object UnwrapIfTransparentProxy(object rcw) {
            if (RemotingServices.IsTransparentProxy(rcw)) { 
                IntPtr punk = Marshal.GetIUnknownForObject(rcw);
                try { 
                    rcw = Marshal.GetObjectForIUnknown(punk); 
                } finally {
                    Marshal.Release(punk); 
                }
            }

            return rcw; 
        }
    } 
 
}
 
#endif

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK