asp tutorials, asp.net tutorials, sample code, and Microsoft news from 15Seconds
Data Access  |   Troubleshooting  |   Security  |   Performance  |   ADSI  |   Upload  |   Email  |   Control Building  |   Component Building  |   Forms  |   XML  |   Web Services  |   ASP.NET  |   .NET Features  |   .NET 2.0  |   App Development  |   App Architecture  |   IIS  |   Wireless
 
Pioneering Active Server
 Power Search








Active News
15 Seconds Weekly Newsletter
• Complete Coverage
• Site Updates
• Upcoming Features

More Free Newsletters
Reference
News
Articles
Archive
Writers
Code Samples
Components
Tools
FAQ
Feedback
Books
Links
DL Archives
Community
Messageboard
List Servers
Mailing List
WebHosts
Consultants
Tech Jobs
15 Seconds
Home
Site Map
Press
Legal
Privacy Policy
internet.commerce














internet.com
IT
Developer
Internet News
Small Business
Personal Technology
International

Search internet.com
Advertise
Corporate Info
Newsletters
Tech Jobs
E-mail Offers

HardwareCentral
Compare products, prices, and stores at Hardware Central!

COM Interop Exposed - Part 2
By Patrick Steele
Rating: 4.5 out of 5
Rate this article


  • email this article to a colleague
  • suggest an article

    This article is a continuation of my first article, COM Interop Exposed. The emphasis of this article will be explaining how to expose your .NET events to COM clients.

    As we did in the first article, a review of how events are done in COM will help you understand how to expose your .NET events to COM. Unfortunately, it's not as easy as simply creating a .NET event and let the COM Callable Wrapper (CCW) do the rest. We'll have to apply a few attributes to get clean COM/.NET integration with events.

    A History of COM Events

    Just like the first article, this history of COM events will focus on interfaces. That's because COM doesn't have native support for true events like .NET does. Instead, COM uses interfaces to simulate events.

    Any "events" mechanism needs a few key features:

    1. An object that supports letting other objects know something has happened (an event). This is typically called the "source" object.
    2. An object that can "subscribe" to these events. This is typically called the "sink" object because it sinks events from the source.
    3. Sink objects talk to the source object to subscribe to the events.
    4. The source object supports more than one sink listening for events. When an event happens, all subscribed sinks will be notified of the event.
    5. Sink objects can subsequently unsubscribe from an event if they no longer want to be notified.

    COM accomplishes the above mechanism through interfaces in a process know as "Connection Point Protocol".

    The Source Object

    In COM's Connection Point Protocol, when an object wants to be a source for events, it defines an interface with methods that will be used to represent the events. Let's say that last part again to make sure it's understood: It defines an interface with methods that will be used to represent the events.

    Assume we have a simple VB6 object that contains a "Click" event and a "MouseOver" event. We'll call this object "CButton". When compiled, the object will already have one interface it implements - the default interface we talked about in the first article. VB6 will automatically create this interface and call it "_CButton". A second interface would be created called "__CButton". This defines the events that CButton exposes.

    The VB6 code would look like this:

    Public Event Click()
    Public Event MouseOver(ByVal x As Integer, ByVal y As Integer)
    

    Once compiled, the COM type library will show the CButton coclass as:

    coclass CButton {
        [default] interface _CButton;
        [default, source] dispinterface __CButton;
    };
    

    The first interface, _CButton, is the default interface created by VB6. The second interface, __CButton, is the default source interface - i.e. the events. It is also created automatically by VB6 and is defined in the type library as:

    dispinterface __CButton {
        properties:
        methods:
            [id(0x00000001)]
            void Click();
            [id(0x00000002)]
            void MouseOver(
                [in] short x, 
                [in] short y);
    };
    

    Notice that the events defined in VB6 show up as methods of the source interface. That will be an important point to understand when we set up our .NET events to be exposed to COM.

    This source object (CButton) also implements a special interface called IConnectionPoint (this doesn't show up in the type library). This COM interface enables other objects to subscribe and unsubscribe to events as well as get a list of events by getting a list of all source interfaces. That's how the VB6 IDE knows what events each object supports - it simply uses the IConnectionPoint interface to enumerate across all source interfaces.

    The Sink Object

    The object that wants to "capture" or "subscribe to" an event does so by implementing the source interface. In the example above, a sink object would implement the __CButton interface and add event handling code in the Click and MouseOver methods. The sink object will pass an instance of itself to the source object through the IConnectionPoint interface. The source will add the reference to a list of other sink objects that are listening for the events.

    Once the source object wants to "raise" an event, it simply goes through the list of subscribers, casts each one to the interface and calls into the sink object through the interface method for the event (remember, the sink object implements the interface that represents all of the events). So in the case of the click event, it would call the "Click" method on all of the sink objects that are contained in its internal list of subscribers. Since the sink object implemented the __CButton interface, it will have code for the Click method.

    As you can see, COM's "Connection Point Protocol" is a fancy way of doing callbacks. Interfaces are used to define the callbacks (i.e. the events). Various built-in COM interfaces provide the means to subscribe to, unsubscribe and call back into the sink objects through the interfaces - thus defining an events mechanism.

    Exposing .NET Events to COM

    Events in .NET are done through delegates. It is beyond the scope of this article to explain delegates. For more details on delegates, see the February 2003 issue of MSDN magazine for an article entitled "A Primer on Creating Type-Safe References to Methods in Visual Basic .NET".

    Let's first look at how a .NET event is exposed to COM without any extra help from us. We'll define a simple "Bug" class that exposes a "Hungry" event - raised when the bug becomes hungry - and a "Found" event - raised when the bug has found an object (it could be food, it could be a tree, etc…):

    [VB.NET]
    Option Strict On
    Option Explicit On 
    
    Namespace BugVB
    
        Public Class Bug
            Public Delegate Sub HungryEventHandler()
            Public Delegate Sub FoundEventHandler(ByVal item As String)
    
            Public Event Hungry As HungryEventHandler
            Public Event Found As FoundEventHandler
    
        End Class
    
    End Namespace
    
    [C#]
    using System;
    
    namespace BugCS
    {
        public class Bug
        {
            public delegate void HungryEventHandler();
            public delegate void FoundEventHandler(string item);
    
            public event HungryEventHandler Hungry;
            public event FoundEventHandler Found;
    
        }
    }
    

    Note that I defined specific delegates for the events in the VB.NET version. This is not required as VB.NET will do this for you automatically, but when I present both C# and VB.NET code that does the same thing, I prefer that the code matches as much as possible and avoid using "helpful" compiler features.

    If we use TLBEXP.EXE to export a COM type library for this object, you'll notice the type library has a coclass for each delegate, but there is no mechanism for hooking up the events. There's no source interface like we saw in the VB6 example earlier. And without a source interface, if you add a reference to the generated type library in VB6, you won't see the events in the Object Browser.

    For a COM object to subscribe to these events using COM's "Connection Point Protocol", a COM object would want an interface that defines the "Hungry" and "Found" methods which it could use to call back into the sink objects. To make our .NET/COM integration smooth, we'll do just that!

    Defining the Interface for Events

    Let's start by defining a regular .NET interface which we'll expose as a COM source interface (the interface for events). There are a few key aspects to point out here:

    • As with other classes and interfaces to be exposed to COM, it needs a unique GUID (see my first article for more on GUID's and COM Interop).
    • For these events to work in VB6, they must be a COM DispInterface type. We can get this by applying the InterfaceTypeAttribute to our interface.
    • For each event, the method name and signature on the source interface method must match the name and signature of the event (not the delegate!) exactly.
    • Each method in the source interface must have the "System.Runtime.InteropServices.DispId" attribute applied with a unique value greater than zero. By providing a unique DispId, COM can call into the method directly without having to do a late-bound call.

    Here's our .NET interface we'll use as our source interface for the Bug object:

    [VB.NET]
        Imports System.Runtime.InteropServices
    
        <Guid("A66356CF-7408-4bf5-B02E-17158FE30DA3"), _
        InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
        Public Interface IBugEvents
            <DispId(1)> _
            Sub Hungry()
    
            <DispId(2)> _
            Sub Found(ByVal item As String)
        End Interface
    
    [C#]
        using System.Runtime.InteropServices;
    
        [Guid("A66356CF-7408-4bf5-B02E-17158FE30DA3")]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        public interface IBugEvents
        {
            [DispId(1)]
            void Hungry();
    
            [DispId(2)]
            void Found(string item);
        }
    

    Now that we have our interface defined, we just need to tell TLBEXP.EXE that this will be our source interface for events. Our "Bug" class doesn't need to implement this interface - it's a "placeholder" used solely for COM interop. This is why it is important that your interface method signatures match your .NET events - since they're only a placeholder for TLBEXP.EXE, .NET will not complain if there is a mismatch. Use the "ComSourceInterfaces" attribute on the Bug class to let COM know what interface will be our source interface for event sinks to hook into. In this case, it will be the IBugEvents interface:

    [VB.NET]
        <ComSourceInterfaces(GetType(IBugEvents))> _
        Public Class Bug
            Public Delegate Sub HungryEventHandler()
            Public Delegate Sub FoundEventHandler(ByVal item As String)
    
            Public Event Hungry As HungryEventHandler
            Public Event Found As FoundEventHandler
    
        End Class
    
    [C#]
        [ComSourceInterfaces(typeof(IBugEvents))]
        public class Bug
        {
            public delegate void HungryEventHandler();
            public delegate void FoundEventHandler(string item);
    
            public event HungryEventHandler Hungry;
            public event FoundEventHandler Found;
    
        }
    

    Now let's look how this .NET class looks when exported to COM:

        coclass Bug {
            [default] interface _Bug;
            interface _Object;
            [default, source] dispinterface IBugEvents;
        };
    

    Perfect! The IBugEvents interface is now listed as our source interface. Please note that I didn't do the extra legwork as described in my first article on COM Interop - I just wanted to cover the COM eventing mechanism in this sample. Now if we look at this type library with the VB6 object browser will see our events just like we expect:

    Summary

    Defining events that will be exposed to COM is pretty easy, but it does take a few steps:

    1. Define your events in your .NET component as required by your design.
    2. Create an interface that contains the method names and signatures of your .NET events.
    3. Use the InterfaceType attribute to mark this interface as an IDispatch interface.
    4. Use the ComSourceInterfaces attribute on your .NET object to define this interface as the "source" interface for COM events.

    About the Author

    Patrick Steele is an independent consultant in southeastern Michigan. He has a broad range of .NET experience including ASP.NET, WinForms, COM+ and COM interop. He's the secretary of the Michigan Great Lakes Area .NET Users group (GANG - http://www.migang.org) and has been recognized by Microsoft for the past five years as a .NET MVP. Contact Patrick through his blog at http://weblogs.asp.net/psteele or patrick (at) mvps (dot) org.

  • Rate This Article
    Not HelpfulMost Helpful
    1 2 3 4 5
    Other Articles
    Jul 21, 2005 - N-Tier Web Applications using ASP.NET 2.0 and SQL Server 2005 - Part 1
    While the .NET Framework made building ASP.NET applications easier then it had ever been in the past, .NET 2.0 builds on that foundation in order to take things to the next level. This article shows you to how to construct an N-Tier ASP.NET 2.0 Web application by leveraging the new features of ASP.NET 2.0 and SQL Server 2005.
    [Read This Article]  [Top]
    Apr 28, 2005 - New Files and Folders in ASP.NET 2.0
    With the release of ASP.NET 2.0, Microsoft has greatly increased the power of ASP.NET by introducing a suite of new features and functionalities. As part of this release, ASP.NET 2.0 also comes with a host of new special files and folders that are meant to be used to implement a specific functionality. This article examines these new files and folders in detail and provides examples that demonstrate how to utilize them to create ASP.NET 2.0 applications.
    [Read This Article]  [Top]
    Mar 10, 2005 - The DataSet Grows Up in ADO.NET 2.0 - Part 2, Cont'd
    Alex Homer continues his detailed look at the major changes to the DataSet class. In this part, he looks at two features that allow developers to work with data in a more structured and efficient way when using the DataSet with a SQL Server 2005 database server.
    [Read This Article]  [Top]
    Mar 9, 2005 - The DataSet Grows Up in ADO.NET 2.0 - Part 2
    Alex Homer continues his detailed look at the major changes to the DataSet class. In this part, he looks at two features that allow developers to work with data in a more structured and efficient way when using the DataSet with a SQL Server 2005 database server.
    [Read This Article]  [Top]
    Mar 3, 2005 - The DataSet Grows Up in ADO.NET 2.0 - Part 1, Cont'd
    In this article, Alex Homer looks at the changes between the version 1.x and version 2.0 DataSet and their associated classes, showing you how you can take advantage of the new features to improve your applications' capabilities and performance.
    [Read This Article]  [Top]
    Mar 2, 2005 - The DataSet Grows Up in ADO.NET 2.0 - Part 1
    In this article, Alex Homer looks at the changes between the version 1.x and version 2.0 DataSet and their associated classes, showing you how you can take advantage of the new features to improve your applications' capabilities and performance.
    [Read This Article]  [Top]
    Feb 16, 2005 - Writing a Custom Membership Provider for the Login Control in ASP.NET 2.0
    In ASP.NET 2.0 and Visual Studio 2005, you can quickly program custom authentication pages with the provided Membership Login controls. In this article, Dina Fleet Berry examines the steps involved in using the Login control with a custom SQL Server membership database.
    [Read This Article]  [Top]
    Dec 29, 2004 - ClickOnce Deployment in .NET Framework 2.0
    In this article, Thiru Thangarathinam examines .NET 2.0's new ClickOnce deployment technology that is designed to ease deployment of Windows forms applications. This new technology not only provides an easy application installation mechanism, it also eases deployment of upgrades to existing applications.
    [Read This Article]  [Top]
    Dec 15, 2004 - A Sneak Peek at ASP.NET 2.0's Administrative Tools
    With ASP.NET 2.0, Microsoft has made great strides in increasing developer productivity and has made implementing previously complex solutions relatively easy. Where this version of ASP.NET really shines, however, is in its new administrative tools that allow developers to spend less time managing the configuration of the servers and software and more time developing great code.
    [Read This Article]  [Top]
    Nov 17, 2004 - The ASP.NET 2.0 TreeView Control
    Thiru Thangarathinam introduces ASP.NET 2.0's new TreeView control which provides a seamless way to consume and display information from hierarchical data sources. The article discusses this new control in depth and explains how to use this feature rich control in your ASP.NET applications.
    [Read This Article]  [Top]
    Mailing List
    Want to receive email when the next article is published? Just Click Here to sign up.

    Support the Active Server Industry



    JupiterOnlineMedia

    internet.comearthweb.comDevx.commediabistro.comGraphics.com

    Search:

    Jupitermedia Corporation has two divisions: Jupiterimages and JupiterOnlineMedia

    Jupitermedia Corporate Info


    Legal Notices, Licensing, Reprints, & Permissions, Privacy Policy.

    Advertise | Newsletters | Tech Jobs | Shopping | E-mail Offers