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

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- CrossAppDomainChannel.cs
- Base64Encoder.cs
- RIPEMD160Managed.cs
- SourceElementsCollection.cs
- XsltQilFactory.cs
- StatusBar.cs
- FixedHyperLink.cs
- FileDialogCustomPlacesCollection.cs
- BatchParser.cs
- ObjectStateManagerMetadata.cs
- BrowserCapabilitiesFactory35.cs
- PagerSettings.cs
- URLAttribute.cs
- SqlFacetAttribute.cs
- CodeDOMUtility.cs
- AssociationType.cs
- ChannelManager.cs
- SqlTopReducer.cs
- CurrentChangingEventArgs.cs
- XmlSchemaAttributeGroup.cs
- Metadata.cs
- RequestQueue.cs
- BinaryReader.cs
- ObjRef.cs
- TypeDescriptionProviderAttribute.cs
- RequestContext.cs
- TimelineCollection.cs
- FontWeights.cs
- MergeExecutor.cs
- TemplateControlBuildProvider.cs
- List.cs
- CultureData.cs
- CqlQuery.cs
- BitmapDecoder.cs
- StyleCollection.cs
- CachedTypeface.cs
- PtsHelper.cs
- ColumnCollection.cs
- IisTraceWebEventProvider.cs
- ConfigUtil.cs
- PrivateFontCollection.cs
- SimpleColumnProvider.cs
- WebPartManagerInternals.cs
- AuthenticatedStream.cs
- Clause.cs
- QuaternionAnimationUsingKeyFrames.cs
- ListManagerBindingsCollection.cs
- DirectoryObjectSecurity.cs
- coordinatorscratchpad.cs
- DelegateSerializationHolder.cs
- ViewStateModeByIdAttribute.cs
- AssemblyBuilder.cs
- SchemaElementLookUpTable.cs
- DocumentOrderComparer.cs
- ScriptIgnoreAttribute.cs
- AxisAngleRotation3D.cs
- ChildDocumentBlock.cs
- SpellCheck.cs
- RangeBase.cs
- DataGridColumnCollection.cs
- Positioning.cs
- StreamFormatter.cs
- DataGridItemCollection.cs
- Literal.cs
- KeyConverter.cs
- CqlLexer.cs
- RtfControlWordInfo.cs
- PageStatePersister.cs
- Base64Decoder.cs
- DispatcherHooks.cs
- DelegateBodyWriter.cs
- Trace.cs
- HttpClientCredentialType.cs
- DynamicField.cs
- Int32EqualityComparer.cs
- XmlArrayItemAttribute.cs
- WebSysDescriptionAttribute.cs
- _SSPIWrapper.cs
- ConnectivityStatus.cs
- DrawToolTipEventArgs.cs
- ResolveCompletedEventArgs.cs
- CodeDomSerializerException.cs
- BufferBuilder.cs
- CodeVariableReferenceExpression.cs
- HtmlInputCheckBox.cs
- TextLineBreak.cs
- List.cs
- TextTreeText.cs
- BigIntegerStorage.cs
- ObjectPropertyMapping.cs
- RichTextBoxConstants.cs
- PostBackOptions.cs
- MdbDataFileEditor.cs
- PropertyMap.cs
- SerialErrors.cs
- ValidationRule.cs
- SimpleHandlerBuildProvider.cs
- NamespaceListProperty.cs
- XmlTextAttribute.cs
- XsdDuration.cs