Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C++
Article

Understanding Classic COM Interoperability With .NET Applications

Rate me:
Please Sign up or sign in to vote.
4.96/5 (207 votes)
24 Jul 2001 1.2M   11.9K   715   169
Discusses how existing COM components can be used from managed code.

(Disclaimer: The information in this article and source code are published in accordance with the Beta 2 bits of the .NET framework SDK - Build 1.0.2914.16)

Ever wondered how all those COM components that you've written through the years play along with the .NET runtime. If you are a diehard COM developer interested in knowing how Classic COM Components are positioned in the .NET world or how COM aware clients could consume .NET components, read on.

Contents

Introduction

After playing around with the .NET Beta 1 & Beta 2 bits, there's no doubt in most developers' mind that the .NET technology is a powerful way to build components and distributed systems for the enterprise. But then, what about the tons of existing reusable COM components that you've built through the last few years, not to mention all those cups of coffee & sleepless nights. Is it the end of all those components in the .NET world?. Will those components work hand-in-hand with the .NET managed runtime?. For those of us who program with COM for a living, and for those of us who live by the 'COM is love' mantra, there is good news. COM is here to stay and .NET managed applications can leverage existing COM components. Certainly, Microsoft wouldn't want to force companies to abandon all their existing components, especially components that were written in one of the most widely used object models for developing both desktop & distributed applications. Classic COM components interoperate with the .NET runtime through an interop layer that will handle all the plumbing between translating messages that pass back and forth between the managed runtime and the COM components operating in the unmanaged realm, and vice versa. Now, let's take a look at the other side of the fence. What if you decide to code your components using a .NET friendly language of your choice targeted for the CLR and still want to be able to consume these .NET components from COM aware clients, say, like VB 6.0 or Classic ASP?. Despair not. COM aware clients will be more than happy to party around with .NET Components through the COM Interop. The tools provided with the .NET framework allow you to expose .NET components to COM aware clients as if they were plain-vanilla COM components. The COM Interop handles all the grunge work and plumbing under the covers. In the first part of the article, we will focus on how you can get COM components to work with .NET applications and then in the latter part, we'll take a look at how you can consume .NET Components from COM aware clients in the unmanaged world. Hopefully, at the end of the article, you'd have gained enough ground to understand how Classic COM and the .NET framework can peacefully co-exist and tango together. So if you're ready, let's take a journey through exploring how Classic COM fits into the grand scheme of things in the .NET world.

Part I : Using Classic COM Components from .NET Applications

Getting started:

We'll begin by taking a look at how you can expose a Classic COM Component to a .NET Application. Our first order of business is to write a simple COM component using ATL that gives us the arrival details for a specific airline. For simplicity, we only return details for the 'Air Scooby IC 5678' airline and return an error for any other airline. That way, you can also take a look at how the error raised by the COM component can be propagated back and caught by the calling .NET client application.

Here's the IDL definition for the IAirlineInfo interface:

.....

interface IAirlineInfo : IDispatch
{

    [id(1), helpstring("method GetAirlineTiming")]
    HRESULT GetAirlineTiming([in] BSTR bstrAirline, 
                             [out,retval] BSTR* pBstrDetails);
    
    [propget, id(2), helpstring("property LocalTimeAtOrlando")]
    HRESULT LocalTimeAtOrlando([out, retval] BSTR *pVal);
    
};
.......

And here's the implementation of the GetAirlineTiming method:

.......

CAirlineInfo::GetAirlineTiming(BSTR bstrAirline, BSTR *pBstrDetails)
{
  _bstr_t bstrQueryAirline(bstrAirline);
  if(NULL == pBstrDetails) return E_POINTER;

  if(_bstr_t("Air Scooby IC 5678") == bstrQueryAirline)
  {
    // Return the timing for this Airline
    *pBstrDetails = _bstr_t(_T("16:45:00 - Will arrive at Terminal 3")).copy();

  }
  else
  {
    // Return an error message if the Airline was not found
    return Error(LPCTSTR(_T("Airline Timings not available for this Airline" )),  
                 __uuidof(AirlineInfo), AIRLINE_NOT_FOUND);
  }
  return S_OK;
  
}
.......

Since we are ready with our component, let's take a look at generating some metadata from the component's type library, so that the .NET client can use this metadata to talk to our component and invoke it's methods.

Generating metadata from the COM Type library

Consuming COM Components from .NET applications

Consuming a Classic COM Component from a .NET application

A .NET application that needs to talk to our COM component cannot directly consume the functionality that's exposed by it. So, we need to generate some metadata. This metadata layer is used by the runtime to glean out type information, so that it can use this type information at runtime to manufacture what is called as a Runtime Callable Wrapper (RCW). The RCW handles the actual activation of the COM object and handles the marshaling requirements when the .NET application interacts with it. The RCW also does tons of other chores like managing object identity, object lifetime, and interface caching. Object lifetime management is a very critical issue here because the .NET runtime moves objects around and garbage collects them. The RCW serves the purpose of giving the .NET application the notion that it is interacting with a managed .NET component and it gives the COM component in the unmanaged space, the impression that it's being called by a good old COM client. The RCW's creation & behavior varies depending on whether you are early binding or late binding to the COM object. Under the hood, the RCW is doing all the hard work and thunking down all the method invocations into corresponding v-table calls into the COM component that lives in the unmanaged world. It's an ambassador of goodwill between the managed world and the unmanaged IUnknown world.

Let's generate the metadata wrapper for our Airline COM component. To do that, we need to use a tool called the TLBIMP.exe. The Type library Importer (TLBIMP) ships with the .NET framework SDK and can be found under the Bin subfolder of your SDK installation. The Type library Importer utility reads a type library and generates the corresponding metadata wrapper containing type information that the .NET runtime can comprehend.

From the DOS command line, type the following command:

TLBIMP AirlineInformation.tlb /out:AirlineMetadata.dll

This command tells the type library importer to read your AirlineInfo COM type library and generate a corresponding metadata wrapper called AirlineMetadata.dll. If everything went through fine, you should see a message indicating that the metadata proxy has been generated from the type library:

Type library imported to E:\COMInteropWithDOTNET\AirlineMetadata.dll

What kind of type information does this generated metadata contain and what does it look like? As COM folks, we've always loved our beloved OleView.exe, at times when we felt we needed to take a peek at a type library's contents, or for the tons of other things that OleView is capable of doing. Fortunately, the .NET SDK ships with a disassembler called ILDASM that allows us to view the metadata & the Intermediate language (IL) code generated for managed assemblies. Every managed assembly contains self-describing metadata and ILDASM is a very useful tool when you need to take a peek at that IL code and metadata. Go ahead and open AirlineMetadata.dll using ILDASM. Take a look at the metadata generated and you will see that the GetAirlineTiming method is listed as a public method of the IAirlineInfo interface that is implemented by the AirlineInfo class. There is also a constructor that gets generated for the AirlineInfo class. The datatypes for the method parameters and return values have also been substituted to take their equivalent .NET counterparts. In our example, the GetAirlineTiming method's parameter with the BSTR datatype has been replaced by the string (an alias for System.String) datatype. Also notice that the parameter that was marked [out,retval] in the GetAirlineTiming method has been converted to the actual return value of the method (returned as a string). Any failure HRESULT values that are returned back from the COM object (in case of an error or failed run-of-the-mill business logic) are thrown back as .NET exceptions.

ILDASM view of metadata

IL Disassembler - a great tool for viewing metadata and MSIL for managed assemblies

Binding to the COM component and invoking its methods from a .NET Application

Now that we have generated the metadata that is required by a .NET client, let's try invoking the GetAirlineTiming method in our COM object from the .NET Client. Here's a simple C# client application that creates the COM object using the metadata that we generated earlier and invokes the GetAirlineTiming method. The first method call invocation should go through fine and we get back the details of airline "Air Scooby IC 5678". Then, we'll pass in an unknown airline, "Air Jughead TX 1234", so that the COM object throws us the AIRLINE_NOT_FOUND error that we defined.

.......

String strAirline = "Air Scooby IC 5678";
String strFoodJunkieAirline = "Air Jughead TX  1234"; 
try 
{ 
   AirlineInfo objAirlineInfo; 
   objAirlineInfo = new AirlineInfo();
   
   // Call the GetAirlineTiming() method 
   System.Console.WriteLine("Details for Airline {0} --> {1}", 
                            strAirline,
                            objAirlineInfo.GetAirlineTiming(strAirline)); 
                            
    // This should make the COM object throw us the
    // AIRLINE_NOT_FOUND error as a COMException
    System.Console.WriteLine("Details for Airline {0} --> {1}", 
                             strFoodJunkieAirline,
                             objAirlineInfo.GetAirlineTiming(
                                        strFoodJunkieAirline)); 
}
catch(COMException e)
{
   System.Console.WriteLine("Oops an error occured !. Error Code is : {0}. 
        Error message is : {1}",e.ErrorCode,e.Message);
}
.......

Here's how the output would look like:

Details for Airline Air Scooby IC 5678 --> 16:45:00 - Will arrive at Terminal 3
Oops an error occured !. Error Code is : -2147221502. 
Error message is : Airline Timings not available for this Airline

Under the hood, the runtime fabricates an RCW and this maps the metadata proxy's class methods and fields to methods and properties exposed by the interface that the COM object implements. One RCW instance is created for each instance of the COM object. The .NET runtime only cares about managing the lifetime of the RCW and garbage collects the RCW. It's the RCW that takes care of maintaining reference counts on the COM object that it's mapped to, thereby, shielding the .NET runtime from managing the reference counts on the actual COM object. As shown in the ILDASM view, the AirlineInfo metadata class is defined under a namespace called AirlineMetadata. This class implements the IAirlineInfo interface. All you need to do is, just create an instance of the AirlineInfo class using the new operator, and call the public class methods of the created object. When the method is invoked, the RCW thunks down the call to the corresponding COM method. The RCW also handles all the marshaling & object lifetime issues. To the .NET client, it looks nothing more than it's actually creating a managed object and calling one of its public class members. Anytime the COM method raises an error, the COM error is trapped by the RCW, and the error is converted into an equivalent COMException class (found in the System.Runtime.InteropServices namespace). Of course, the COM object still needs to implement the ISupportErrorInfo and IErrorInfo interfaces for this error propagation to work, so that the RCW knows that the object provides extended error information. The error is caught by the .NET client using the usual try-catch exception handling mechanism and has access to the actual error number, description, the source of the exception and other details that would have been available to any COM aware client. You could also return standard HRESULTs back and the RCW will take care of mapping them to the corresponding .NET exceptions to throw back to the client. For example, if you were to return a HRESULT of E_NOTIMPL from your COM method, then the RCW will map this to the .NET NotImplementedException exception and throw an exception of that type.

Please refer to the Exception Handling section in this article, to learn more about how .NET exceptions are mapped to COM HRESULTs.

Accessing other supported interfaces and Dynamic Type Discovery

How does the classic QueryInterface scenario work from the perspective of the .NET client when it needs to check if the COM Object implements a specific interface? To QI for another interface, all you need to do is cast the object to the interface that you are querying for, and if it succeeds, voilà, your QI has succeeded as well. In case you attempt to cast the object to some arbitrary interface that the object does not support, a System.InvalidCastException exception is thrown, indicating that the QI has failed. It's that simple. Again, the RCW does all the hard work under the covers. It's a lot like how the VB runtime shields us from having to write any explicit QueryInterface related code and simply does the QI for you when you set one object type to an object of another associated type.

An alternate way to check if the object instance that you are currently holding supports or implements a specific interface type is to use C#'s 'is' operator. The 'is' operator does runtime type checking to see if the object can be cast safely to a specific type. If it returns true, then you can safely perform a cast to get the QI done for you. This way the RCW ensures that you are casting to only interfaces that are implemented by the COM object and not just any arbitrary interface type. You can also use C#'s 'as' operator to cast from one type to another compatible type as shown in the example below. These simple constructs are all what you need to keep swinging between interfaces that the COM object supports in a type safe manner.

C#
.......
try
{
    AirlineInfo objAirlineInfo = null;
    IAirportFacilitiesInfo objFacilitiesInfo = null;

    // Create a new AirlineInfo object
    objAirlineInfo = new AirlineInfo();

    // Invoke the GetAirlineTiming method
    String strDetails = objAirlineInfo.GetAirlineTiming(strAirline);

    // Check to see if the AirlineInfo object supports the 
    // IAirportFacilitiesInfo interface using C#'s 'is' operator
    if(objAirlineInfo is IAirportFacilitiesInfo) 
    {
        // Perform a cast to get the QI done
        objFacilitiesInfo = (IAirportFacilitiesInfo)objAirlineInfo;

        // There's always more than one way to skin a cat
        // You could even perform the cast using C#'s 'as' operator
        objFacilitiesInfo = objAirlineInfo as IAirportFacilitiesInfo;

        //Invoke a method on the IAirportFacilitiesInfo interface
        System.Console.WriteLine("{0}", 
               objFacilitiesInfo.GetInternetCafeLocations());
        
    }

    // Let's check against an arbitrary interface type
    if(objAirlineInfo is IJunkInterface) 
    {
        System.Console.WriteLine("We should never get here ");
    }
    else
    {
        System.Console.WriteLine("I'm sorry I don't implement" + 
                              " the IJunkInterface interface ");
    }

    // And now let's ask for some trouble and have the
    // interop throw us an invalid cast exception.
    IJunkInterface objJunk = null;
    objJunk = (IJunkInterface)objAirlineInfo;

}/* end try */
catch(InvalidCastException eCast)
{
    System.Console.WriteLine("Here comes trouble" + 
                             " ... Error Message : {0}", 
                             eCast.Message);   
    
}/* end catch */
.......

Here's how the output would look like:

Your nearest Internet Cafe is at Pavilion 3 in Terminal 2 - 
                             John Doe's Sip 'N' Browse Cafe
I'm sorry I don't implement the IJunkInterface interface
Here comes trouble ... Error Message : 
     An exception of type System.InvalidCastException was thrown.

Late Binding to COM Objects

All the examples that you saw above used the metadata proxy to early bind the .NET Client to the COM object. Though early binding provides a whole smorgasbord of benefits like strong type checking at compile time, providing auto-completion capabilities from type-information for development tools, and of course, better performance, there may be instances when you really need to late bind to a Classic COM object when you don't have the compile time metadata for the COM object that you are binding to. You can late bind to a COM object through a mechanism called Reflection. This does not apply to COM objects alone. Even .NET managed objects can be late bound and loaded using Reflection. Also, if your object implements a pure dispinterface only, then you are pretty much limited to only using Reflection to activate your object and invoke methods on its interface. For late binding to a COM object, you need to know the object's ProgID or CLSID. The CreateInstance static method of the System.Activator class allows you to specify the Type information for a specific class and it will automatically create an object of that specific type. But what we really have is a COM ProgID and COM CLSID for our COM object and not true .NET Type Information. So we need to get the Type information from the ProgID or CLSID using the GetTypeFromProgID or GetTypeFromCLSID static methods of the System.Type class. The System.Type class is one of the core enablers for Reflection. After creating an instance of the object using Activator.CreateInstance, you can invoke any of the methods/properties supported by the object using the System.Type.InvokeMember method of the Type object that you got back from Type.GetTypeFromProgID or Type.GetTypeFromCLSID. All you need to know is, the name of the method or property and the kind of parameters that the method call accepts. The parameters are bundled up into a generic System.Object array and passed away to the method. You would also need to set the appropriate binding flags depending on whether you are invoking a method or getting/setting the value of a property. That's all there is to late binding to a COM object.

C#
.......
try
{

   object objAirlineLateBound;
   Type objTypeAirline;
   
   //  Create an object array containing
   // the input parameters for the method
   object[] arrayInputParams= { "Air Scooby IC 5678" };
   
   //Get the type information from the progid
   objTypeAirline = 
     Type.GetTypeFromProgID("AirlineInformation.AirlineInfo");
   
   // Here's how you use the COM CLSID
   // to get the associated .NET System.Type
   // objTypeAirline = Type.GetTypeFromCLSID(new Guid(
                       "{F29EAEEE-D445-403B-B89E-C8C502B115D8}"));

   
   // Create an instance of the object
   objAirlineLateBound = Activator.CreateInstance(objTypeAirline);

   // Invoke the 'GetAirlineTiming' method
   String str =  (String)objTypeAirline.InvokeMember("GetAirlineTiming", 
                                    BindingFlags.Default | 
                                    BindingFlags.InvokeMethod, 
                                    null, 
                                    objAirlineLateBound, 
                                    arrayInputParams);

    System.Console.WriteLine("Late Bound Call" + 
              " - Air Scooby Arrives at : {0}",str);

    // Get the value of the 'LocalTimeAtOrlando' property
    String strTime = (String)objTypeAirline.InvokeMember("LocalTimeAtOrlando",
                                    BindingFlags.Default | 
                                    BindingFlags.GetProperty, 
                                    null, 
                                    objAirlineLateBound,
                                    new object[]{});

    Console.WriteLine ("Late Bound Call - Local" + 
                       " Time at Orlando,Florida is: {0}", 
                       strTime);

}/* end try */
catch(COMException e)
{
    System.Console.WriteLine("Error code : {0}, Error message : {1}", 
                              e.ErrorCode, e.Message);
}/* end catch */
.......

Take a look at the output that you'd get:

Late Bound Call - Air Scooby Arrives at 16:45:00 - Will arrive at Terminal 3
Late Bound Call - Local Time at Orlando,Florida is: Sun Jul 15 16:50:01 2001

Event Handling - Connection Points in Classic COM Vs Delegate Event Model in .NET

The Connection Points event handling mechanism, as you know, is one of the primary enablers for bi-directional communication between your COM components and the consumers of your components. Just to jog your memory, I'll brief you a little bit on the event handling mechanism in Classic COM Components. Typically, COM components that support event notifications have what is called, an outgoing interface. The outgoing interface is used by the component to call into the client when a specific event has occurred. Outgoing interfaces are marked with the [source] attribute in the coclass section of the component's IDL file. The [source] attribute in the IDL allows development tools and IDEs to parse the typelibrary to check to see if the object supports an outgoing interface. Consumers or clients of these components usually set up a sink object, which implements this outgoing interface. An interface pointer to this sink object is passed by the client to the component. The component stashes away this interface pointer in typically, something like a map that contains a list of outgoing interface pointers to sink objects that are interested in receiving notifications from the component. Whenever a component needs to raise an event, it uses the map to get a list of interface pointers to the sink objects that have subscribed for notifications. It then notifies them by calling the respective method on the outgoing interface implemented by the sink object.

Connection Points in Classic COM

Essentially, a COM object that supports outgoing interfaces, implements the IConnectionPointContainer interface. A client that wants to receive event notifications does a QI on the COM object for the IConnectionPointContainer interface to see if it supports outgoing interfaces. If the QI fails, then the object does not support events. If the QI succeeds, the client calls the FindConnectionPoint method (could also call EnumConnectionPoints) on the IConnectionPointContainer interface by passing it the IID of the outgoing interface. If such an interface is supported, the client receives back an IConnectionPoint interface pointer corresponding to the outgoing interface. It then calls the IConnectionPoint::Advise method and passes to it, the sink object's IUnknown pointer. The COM object adds this IUnknown pointer to its map to keep a list of the sink objects that have subscribed for notifications. The client gets back a cookie from the COM object that it can subsequently use to revoke event notifications. When the COM object needs to raise events, it iterates through the map, gets a list of all the sink object interface pointers and calls the corresponding event methods on the outgoing interface (implemented by the sink object). When a client no longer desires to receive notifications, it removes itself from the object's map by calling the IConnectionPoint::Unadvise method by passing it the cookie that it received earlier on the IConnectionPoint::Advise call.

Connection Points in Classic COM

Connection Points in Classic COM

In simple terms, that's how the event handling mechanism & bi-directional communication works in Classic COM components. Most books that teach COM programming usually have a full chapter dedicated to explain this architecture and you might want to look them up to further your understanding in this topic.

Creating an ATL COM Component that sources events

Let's take a look at how the Connection Points event handling mechanism in COM translates to the delegate event handling mechanism in the .NET world. We'll take a look at how you can use .NET managed event sinks to catch event notifications sent from COM objects. To get started, let's take a look at the COM object that's going to source events to your .NET application. Let's put together a simple COM object that will page your .NET application, whenever an airline arrives at a fictitious airport called John Doe International Airport. We will subscribe to this paging service from our .NET application so that we get paged whenever an airplane taxies down on John Doe's runway.

AirlineArrivalPager Classes (Class View)

We'll create an ATL EXE project that hosts an object called AirlineArrivalPager. The AirlineArrivalPager object supports an incoming interface called IAirlineArrivalPager and an outgoing interface called _IAirlineArrivalPagerEvents. Here's the interface definition of the _IAirlineArrivalPagerEvents outgoing interface. This interface is marked with the [source] attribute in the coclass definition.

.....
    
interface IAirlineArrivalPager : IDispatch
{
    [id(1), helpstring("method AddArrivalDetails")] 
       HRESULT AddArrivalDetails([in] BSTR bstrAirlineName, 
       [in] BSTR bstrArrivalTerminal);
};

....

dispinterface _IAirlineArrivalPagerEvents
{
    properties:
    methods:
       [id(1), helpstring("method OnAirlineArrivedEvent")] 
            HRESULT OnAirlineArrivedEvent([in] BSTR bstrAirlineName, 
                                          [in] BSTR bstrArrivalTerminal);
};

....

coclass AirlineArrivalPager
{
    [default] interface IAirlineArrivalPager;
    [default, source] dispinterface _IAirlineArrivalPagerEvents;
};

.......

Take a look at the implementation of the incoming IAirlineArrivalPager interface's AddArrivalDetails method:

.......
STDMETHODIMP CAirlineArrivalPager::AddArrivalDetails(
             BSTR bstrAirlineName,BSTR bstrArrivalTerminal)
{
    // Notify all subscribers that an Airline has hit the tarmac
    Fire_OnAirlineArrivedEvent(bstrAirlineName,bstrArrivalTerminal);

    // Return the status to the caller
    return S_OK;
}
.......

The implementation of this method uses the Fire_OnAirlineArrivedEvent helper method to notify all sink objects implementing _IAirlineArrivalPagerEvents that have subscribed for event notifications. The Fire_OnAirlineArrivedEvent is a method of the helper proxy class derived from IConnectionPointImpl that is generated automatically by the ATL Implement Connection point wizard. Essentially, it iterates through the map where it stored the interface pointers to the sink objects when IConnectionPoint::Advise was called, and uses these interface pointers to call the event notification method (OnAirlineArrivedEvent) implemented by the client's sink object.

If you were a C++ programmer coding a COM aware client application to receive notifications, you would set up a sink object in the client application that implements the _IAirlineArrivalPagerEvents interface. You would then create the AirlineArrivalPager object and pass it the IUnknown interface pointer of the sink object through a call to IConnectionPoint::Advise or use a helper method such as AtlAdvise to wire your sink to the object that raises events, so that you can receive event notifications. With VB 6.0, it's as simple as using the WithEvents keyword in your declaration and defining a handler function for receiving notifications. VB will do all the hard work under the covers to hook up the notifications made on the outgoing interface to the appropriate handler function.

Event handling using Delegates

If you are already familiar with how delegates are used in .NET, you might want to skip this section and swing by to the next section. Event handling in .NET is primarily based on the Delegate Event model. A delegate is something akin to function pointers that we use in C/C++. The delegate based Event model was popularized by the simplicity of its use, right from the WFC (Windows Foundation Classes in Visual J++) days. Delegates allow an event raised by any component to be connected to a handler function or method of any other component as long as the function signatures of the handler function or method matches the exact signature of that of the delegate. Take a look at this simple example below that shows you how you can put delegates to action.

C#
// Here's the SayGoodMorning delegate
delegate string SayGoodMorning();

public class HelloWorld
{
   public string SpeakEnglish() {
    return "Good Morning";
   }
   
   public string SpeakFrench() {
        return "Bonjour";
   }
   
   public static void Main(String[] args) {

    HelloWorld obj = new HelloWorld();

    // Associate the delegate with a method reference
    SayGoodMorning english = new SayGoodMorning(obj.SpeakEnglish);
    SayGoodMorning french = new SayGoodMorning(obj.SpeakFrench);

    // Invoke the delegate
    System.Console.WriteLine(english());
    System.Console.WriteLine(french());

  }

}/* end class */

Here's the output that you get back:

Good Morning
Bonjour

In the example above, we declare a delegate called SayGoodMorning. Then we wire the delegate to reference the SpeakEnglish and SpeakFrench methods of the HelloWorld object. All that is required is that the SpeakEnglish and SpeakFrench methods have the same signature as that of the SayGoodMorning delegate. The reference is typically made by instantiating the delegate as if it were an object and passing in the referenced method as its parameter. The referenced method could either be an instance method or a static method of a class. The delegate maintains the reference it needs to call the right handler for the event. This makes delegates first class object-oriented citizens and they are also type-safe and secure to deal with. The .NET event-handling model is based primarily on the delegate event model. Take a look at the following example:

C#
......

// Create a Button
private System.Windows.Forms.Button AngryButton = new Button();

....

// Add a delegate to the button's Click event list
AngryButton.Click += new System.EventHandler(AngryButton_Click);

.....

// Here's the handler function that the delegate references
protected void AngryButton_Click(object sender,EventArgs e)
{
   MessageBox.Show("Please Stop clicking me !!");
}

.......

When your application deals with controls and wants to receive specific notifications, it creates a new instance of an EventHandler delegate that contains a reference to the actual handler function that will handle the events raised by the control. In the example shown above, the EventHandler delegate contains a reference to the AngryButton_Click method. The AngryButton_Click method needs to have the same method signature as that of the EventHandler delegate. Here's how the signature of the System.EventHandler delegate looks like:

C#
public delegate void EventHandler(object sender, EventArgs e);

The EventHandler delegate instance will then have to be added to the Click event's list of delegate instances. Delegates that extend the System.MulticastDelegate allow you to add multiple handler functions to the delegate's invocation list using C# operators such as += and -= which are wrappers for the Delegate.Combine and Delegate.Remove methods. Using an event provides users with a foolproof scheme to only add or remove delegate instances to the event using the += and -= operator and not accidentally overwrite the invocation list. When the control raises an event, each of the delegates that have been added to the button's Click event will be invoked and the delegate will route it to the correct handler function that it references.

In our example, whenever the Click Event occurs in the button the call will be routed to the AngryButton_Click method. I guess this gives you a fairly good idea on the role played by delegates and events in the event-handling mechanism in the .NET framework. The reason I explained to you how delegates work is because it's one of the primary enablers of the .NET event handling model and it's important to understand this to appreciate how .NET applications use delegates to subscribe to event notifications from Classic COM Components.

Sinking Unmanaged COM Events in a .NET Application

VB Client - Control Tower App

Here's a simple VB Client application that assumes the role of the Control Tower at John Doe International Airport and calls the AddArrivalDetails method in the incoming interface. The implementation of this method in turn triggers the event notifications that are subsequently caught by the handler functions in the .NET application that have subscribed for OnAirlineArrivedEvent event notifications. The AirlineArrivalPager COM object is itself a singleton object hosted in an out-of-proc COM server. So, the same instance of the object services both the VB based Control tower application (that triggers events) and the .NET pager applications that have subscribed for OnAirlineArrivedEvent event notifications.

VB
Dim AirlinePager As New AIRLINENOTIFYLib.AirlineArrivalPager

Private Sub AirlineArrived_Click()
    AirlinePager.AddArrivalDetails Me.AirlineName, Me.ArrivalTerminal
End Sub

With that said, let's see how a .NET managed application receives event notifications generated by the AirlineArrivalPager COM object. Firstly, you need to generate a .NET metadata proxy from the COM object's typelibrary, so that it can be consumed by a .NET application. Let's use the Type Library Importer (TLBIMP) to generate the metadata proxy assembly for us.

tlbimp AirlineNotify.tlb /out:AirlineNotifyMetadata.dll

This metadata proxy will be referenced in your .NET application. Here's a simple .NET Windows Forms application that subscribes to event notifications from the AirlineArrivalPager COM component using delegates.

Windows Forms Pager Application

C#
 ......
 
 using AirlineNotifyMetadata;

 public class AirlineNotifyForm : System.WinForms.Form
 {
   private System.Windows.Forms.CheckBox checkBoxPaging;
   private System.Windows.Forms.ListBox listPager;
   private AirlineArrivalPager m_pager = null;
   
   ......
   
   public AirlineNotifyForm() {
        
     .....

     // Subscribe to event notifications from
     // the AirlineArrivalPager component
     subscribePaging();
            
   }

   ......

   void subscribePaging() {
   
     // Create an AirlineArrivalPager object
     m_pager = new AirlineArrivalPager();

     // Add the delegate instance that references
     // the OnMyPagerNotify method
     // to the OnAirlineArrivedEvent event list (ICP::Advise)
     m_pager.OnAirlineArrivedEvent += 
       new _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(
       OnMyPagerNotify);
         
  }/* end subscribePaging */


  protected void checkBoxPaging_CheckedChanged (object sender, 
                                                System.EventArgs e) {
  
     if(checkBoxPaging.Checked) {
     
       // If checked, add the delegate instance
       // that references OnMyPagerNotify
       // to the OnAirlineArrivedEvent event list (ICP::Advise)
       m_pager.OnAirlineArrivedEvent += 
         new _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(
         OnMyPagerNotify);
                       
     }
     else {
     
       // If Unchecked, remove the delegate
       // instance that references OnMyPagerNotify
       // from the OnAirlineArrivedEvent event list (ICP::Unadvise)
       m_pager.OnAirlineArrivedEvent -= new 
        _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(
         OnMyPagerNotify);
           
     }
            
  }/* end checkBoxPaging_CheckedChanged */
        

  public int OnMyPagerNotify(String strAirline, String strTerminal) {
  
     StringBuilder strDetails = new StringBuilder("Airline ");
     strDetails.Append(strAirline);
     strDetails.Append(" has arrived in ");
     strDetails.Append(strTerminal);
     listPager.Items.Insert(0,strDetails);
     return 0;


   }/* end OnMyPagerNotify */

}/* end class */

The line of code that is most important here is the line:

C#
m_pager.OnAirlineArrivedEvent += new 
  _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(
   OnMyPagerNotify);

If you understand the semantics of how delegates work, you should have absolutely no problem comprehending what's going on here. What you're doing is adding the _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler delegate instance that references the OnMyPagerNotify method to the OnAirlineArrivedEvent event list. Usually the name of the event (OnAirlineArrivedEvent) is the same as the method name in the outgoing interface. The delegate name (_IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler) usually follows the pattern InterfaceName_EventNameEventHandler. That's all there is to receiving event notifications from COM components. All you need to do is create an instance of the component and then add a delegate referencing your handler function to the event list. Effectively, what you are doing here is something that's analogous to the IConnectionPoint::Advise in the COM world. Whenever the OnAirlineArrivedEvent event is raised by the COM component, the OnMyPagerNotify method will be called to handle the event notification. It's that simple in .NET, to wire a handler sink to receive event notifications from a COM object that sources events.

How the Connection Point Event Handling mechanism in Classic COM maps to the Delegate event handling mechanism in .NET

How the Connection Point Event Handling mechanism in Classic COM maps to the Delegate event handling mechanism in .NET

When you no longer want to receive notifications, you can remove the delegate from the event list by calling:

C#
m_pager.OnAirlineArrivedEvent -= new 
  _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(OnMyPagerNotify);

This is analogous to the IConnectionPoint::Unadvise method call that revokes further notifications by removing your sink object's interface pointer from the map using the cookie that you received in the Advise call. But, who handles the mapping between the Connection point event handling model in Classic COM and delegate event model in .NET? The metadata proxy generated by the Typelibrary importer (TLBIMP) contains classes that act as adaptors to wire the Connection point Event Model in the unmanaged world to the Delegate based event model in the .NET world via the RCW stub that is created at runtime. If you are interested in examining what happens under the hood, I encourage you to open up the metadata proxy (AirlineNotifyMetadata.dll) using the IL Diassembler (ILDASM) and examine the MSIL code for the various methods in the helper classes.

Using COM Collections from .NET Applications

Using COM based Collections allows you to categorize objects together as a part of the group that exhibit similar behavior. For example, a BookCollection could model all the books in a library. Each Book object stored in this collection could represent the details of the book such as the Author, ISBN etc. Iterating through the collection is extremely simple and allows you to get back objects on demand that have been added to the collection. There are other methods to model collections too, such as using SAFEARRAYs. The problem with a SAFEARRAY is that, if the collection is large, it involves moving entire data chunks that the SAFEARRAY represents across to the client. Using collections allows you to get data on demand. Also, it's much more elegant to iterate through a collection from clients such as VB, using the For Each..Next syntax. If you have existing COM objects that represent COM based collections, they would continue to work well with .NET applications. These collections can be enumerated just as easily by .NET clients. We'll soon see how. If you are already familiar with how collections work in Classic COM, you might want to skip this section and go to the next section.

Creating a COM collection Component using ATL

For the VB folks, who feel more at home with churning out COM components with VB, you might want to skip this section and go to the Creating a COM collection Component using VB section. First, let's put together a simple COM Component using ATL that models an ice cream collection. The Collection class represents the menu at an ice cream parlor that contains a variety of ice cream flavors. Take a look at the IDL file for this component.

C#
[
  ....
]
interface IIceCreamMenu : IDispatch
{
    [propget, id(1), helpstring("property Count")] 
              HRESULT Count([out, retval] long *pVal);

    [propget, id(DISPID_NEWENUM), 
              helpstring("property _NewEnum"), restricted] 
              HRESULT _NewEnum([out, retval] LPUNKNOWN *pVal);

    [propget, id(DISPID_VALUE), helpstring("property Item")] 
              HRESULT Item([in] long lIndex, 
              [out, retval] VARIANT *pVal);

    [id(2), helpstring("method AddFlavortoMenu")]
            HRESULT AddFlavortoMenu([in] BSTR bstrNewFlavor);
};

The Count, _NewEnum, and Item property are standard properties that every COM Collection supports. The _NewEnum property allows you to enumerate through the collection using constructs like For Each .. Next in VB and is always assigned a DISPID of DISPID_NEWENUM (-4) to indicate that it's the enumerator for the collection. This property usually returns an IUnknown interface pointer to an object that implements the IEnumVariant interface. The IEnumVariant interface provides all the methods that you need (such a Next, Skip, Reset, Clone) for enumerating a collection containing VARIANTs. The Item property is assigned a DISPID of DISPID_VALUE (0) to indicate that this is the default property to be used, if the property name is omitted. The Item property allows you to locate an item in the collection using an index. The index itself can be any type based on your business model. For example, a Book Collection could provide an ISBN Number as a string for its Item Index that could act as a Key Value in an STL map to locate the corresponding book. In the ice cream menu example, we use an Index of type long to locate an ice cream at a specified index. Also, since we have modeled our collection based on an STL vector, an index of type long is convenient to access a specific element in the vector. The base class ICollectionOnSTLImpl<> that our IceCreamMenu collection component derives from provides us with a default implementation of Item based on an index of type long. The Count property returns the number of elements in the collection. All 3 of them are read-only properties. Other than these properties, you could add any number of helper methods that allow you to add, remove and update elements in your collection. Take a look at the code for the IceCreamMenu collection component below, coded using ATL.

.......

//forward definition
class _CopyPolicyIceCream;

// Define an STL vector to hold all the Icecream flavors
typedef vector<_bstr_t> ICECREAM_MENU_VECTOR;

// Define a COM Enumerator based on our ICECREAM_MENU_VECTOR
typedef CComEnumOnSTL< IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,
                  _CopyPolicyIceCream, ICECREAM_MENU_VECTOR > VarEnum;

// Collection Class Helper for STL based containers
typedef ICollectionOnSTLImpl< IIceCreamMenu, 
                 ICECREAM_MENU_VECTOR, VARIANT,
                 _CopyPolicyIceCream, 
                 VarEnum > IceCreamCollectionImpl;

// Simulate Deep copy semantics for the elements in our collection 
class _CopyPolicyIceCream
{
public:

  static HRESULT copy(VARIANT* pVarDest,_bstr_t* bstrIceCreamFlavor)
  {
    // Assign to a CComVariant 
    CComVariant varFlavor((TCHAR *)(*bstrIceCreamFlavor)); 

    // Perform a deep copy
    return ::VariantCopy(pVarDest,&varFlavor);
  }

  static void init(VARIANT* pVar) 
  {
    pVar->vt = VT_EMPTY;
  }

  static void destroy(VARIANT* pVar) 
  {
    VariantClear(pVar);
  }

};

// Begin IceCreamMenu Class

class ATL_NO_VTABLE CIceCreamMenu : 
    public CComObjectRootEx< CComSingleThreadModel >,
    public CComCoClass< CIceCreamMenu, &CLSID_IceCreamMenu >,
    public ISupportErrorInfo,
    public IDispatchImpl< IceCreamCollectionImpl, 
                         &IID_IIceCreamMenu, 
                         &LIBID_ICECREAMPARLORLib, 1, 0 >
{
public:
               
          ...........
          

// IIceCreamMenu
public:
      STDMETHOD(AddFlavortoMenu)(/*[in]*/ BSTR bstrNewFlavor);

      // These three methods are not required because the 
      // base class ICollectionOnSTLImpl<> provides us with a default
      // implementation.
      
      // STDMETHOD(get_Item)(/*[in]*/ VARIANT Index, 
      //                     /*[out, retval]*/ VARIANT *pVal);
      // STDMETHOD(get__NewEnum)(/*[out, retval]*/ LPUNKNOWN *pVal);
      // STDMETHOD(get_Count)(/*[out, retval]*/ long *pVal);

};

The ICollectionOnSTLImpl<> class that the CIceCreamMenu class extends provides the default implementation for the Item, Count, and _NewEnum collection properties. The underlying collection type that it represents (in our case, a vector containing _bstr_t strings) is denoted by the m_coll instance. To add items to the collection, you just need to populate the m_coll with the elements in your collection. This is what the FinalConstruct attempts to do by adding some ice cream flavors into the vector< _bstr_t > represented by m_coll.

.......

HRESULT CIceCreamMenu::FinalConstruct()
{
    // Fill up the menu with some flavors
    m_coll.push_back(_bstr_t(_T("Chocolate Almond Fudge")));
    m_coll.push_back(_bstr_t(_T("Peach Melba")));
    m_coll.push_back(_bstr_t(_T("Black Currant")));
    m_coll.push_back(_bstr_t(_T("Strawberry")));
    m_coll.push_back(_bstr_t(_T("Butterscotch")));
    m_coll.push_back(_bstr_t(_T("Mint Chocolate Chip")));
    return S_OK;
}

STDMETHODIMP CIceCreamMenu::AddFlavortoMenu(BSTR bstrNewFlavor)
{
    m_coll.push_back(_bstr_t(bstrNewFlavor));
    return S_OK;
}
Creating a COM collection Component using VB

For the benefit of the VB folks, here's an equivalent implementation of the IceCreamMenu COM collection class in VB. Be sure to tag the NewEnum function with a DISPID of -4 (DISPID_NEWENUM). You can do this by using the Tools->Procedure Attributes dialog box in the VB IDE. You need to set the Procedure ID for NewEnum to -4 and also make sure that you turn on the Hide this member attribute check box.

VB
Option Explicit
Private mIceCreamFlavors As Collection

Private Sub Class_Initialize()
    Set mIceCreamFlavors = New Collection
    mIceCreamFlavors.Add "Chocolate Almond Fudge"
    mIceCreamFlavors.Add "Peach Melba"
    mIceCreamFlavors.Add "Black Currant"
    mIceCreamFlavors.Add "Strawberry"
    mIceCreamFlavors.Add "Butterscotch"
    mIceCreamFlavors.Add "Mint Chocolate Chip"
End Sub

Public Function Count() As Integer
    Count = mIceCreamFlavors.Count
End Function

Public Function Item(varIndex As Variant) As String
    Item = mIceCreamFlavors(varIndex)
End Function

Public Function NewEnum() As IEnumVARIANT
    Set NewEnum = mIceCreamFlavors.[_NewEnum]
End Function

Public Function AddFlavortoMenu(strNewFlavor As String)
    mIceCreamFlavors.Add strNewFlavor
End Function
Consuming COM Collections in a .NET Application

To get a .NET application to consume the collection COM component that we just coded in the last section, we will need to generate the .NET metadata proxy from the component's typelibrary. You can do this using the following command from the command-line:

tlbimp IceCreamParlor.tlb /out:IceCreamMenuMetadata.dll

Now open the IceCreamMenuMetadata.dll using the IL Disassembler (ILDASM) and take a look at the methods generated for the IceCreamMenu class.

Metadata proxy generated by TLBIMP for the IceCreamMenu collection component

Metadata proxy generated by TLBIMP for the IceCreamMenu collection component

The IceCreamMenu class implements two interfaces: the IIceCreamMenu interface and the System.Collections.IEnumerable interface. Implementing the IEnumerable interface tells consumers that the class allows you to enumerate through elements in its collection. The IIceCreamMenu interface in the metadata proxy object has the Count and Item property preserved from the COM component's IIceCreamMenu interface. But what has TLBIMP done to the _NewEnum property that represents our collection's enumerator?. It has replaced that with the GetEnumerator() method that returns the IEnumerator interface of the object that handles the actual enumeration.

Consuming COM collections in .NET Applications

Consuming COM collections in .NET Applications

Since the IceCreamMenu class implements the IEnumerable interface, you could use extremely simple constructs such as C#'s foreach statement to enumerate through elements in such a collection. Here's how you could consume the IceCreamMenu collection COM component from your .NET application.

C#
using System;
using IceCreamMenuMetadata;

public class IceCreamMenuClient
{
    public static void Main(String[] args)
    {
      // Create an instance of the Collection class
      IceCreamMenu menu = new IceCreamMenu();

      // Add a few more flavors to the Menu
      menu.AddFlavortoMenu("Blueberry");
      menu.AddFlavortoMenu("Chocolate Chip");
      
      
      // Use the foreach statement to iterate through 
      // elements in the collection 
      foreach(Object objFlavor in menu)
      {
        System.Console.WriteLine("{0}",objFlavor);
      }
        
   }/* end Main */

}/* end class IceCreamMenuClient */

You can compile the above code using the following command-line:

csc /target:exe /r:IceCreamMenuMetadata.dll 
    /out:IceCreamMenuClient.exe IceCreamMenuClient.cs

Here's the output that you get:

Chocolate Almond Fudge
Peach Melba
Black Currant
Strawberry
Butterscotch
Mint Chocolate Chip
Blueberry
Chocolate Chip

Take a look at how easy it is to use C#'s foreach construct to iterate through the elements in the collection. Again, the RCW powers the enumeration under the covers by translating IEnumVARIANT based COM Collection semantics into a representation that can be serviced by IEnumerator based methods and frees us from all the marshaling rigmarole.

Enumerating elements in a .NET Collection

The IEnumerable and the IEnumerator interfaces are the primary enablers for enumerating collections in the .NET world. As mentioned earlier, implementing the IEnumerable interface tells consumers that the object allows you to enumerate through elements in its collection The IEnumerator interface consists of two methods: MoveNext and Reset, and one property: Current, that needs to be implemented by the object that provides the Enumerator for the collection. If your class is based on a simple collection such as an array, you need to just make an index move back and forth across the array for the MoveNext implementation. You would need to reset the index to point to the start of the array in the implementation of Reset and would need to return the array element at the current index position for the Current property's implementation. Take a look at this example below so that things become a little clearer as to what these two interfaces are required to do if you were to implement a .NET class that modeled a collection and allowed enumeration.

C#
using System;
using System.Collections;

public class SevenDwarfs : IEnumerable , IEnumerator
{
    private int nCurrentPos = -1;
    private string[] strArrayDwarfs =  
            new String[7] {"Doc", "Dopey", "Happy", "Grumpy",
                           "Sleepy", "Sneezy" , "Bashful"};
    SevenDwarfs() {}

    // Method : IEnumerable.GetEnumerator
    // Return an appropriate Enumerator for the collection
    public IEnumerator GetEnumerator() 
    {
       return (IEnumerator)this;
    }

    // Method : IEnumerator.MoveNext
    // Move the enumerator to the next element in the collection
    // and return boolean status whether we still have elements to 
    // enumerate
    public bool MoveNext() 
    {
      if(nCurrentPos < strArrayDwarfs.Length - 1)
      {
         nCurrentPos++;
         return true;
      }
      else
      {
        return false;
      }
    }

    // Method : IEnumerator.Reset
    // Reset the enumerator to the beginning of the collection
    public void Reset()
    {
      nCurrentPos = -1;
    }

    // Method : IEnumerator.Current
    // Return the element at the current enumerator position
    public object Current
    {
       get
       {
         return strArrayDwarfs[nCurrentPos];
       }
    }

    public static void Main(String[] args)
    {
       // Create an instance of the SevenDwarfs object
       SevenDwarfs SnowWhitesDwarfs = new SevenDwarfs();
       
       // Enumerate through the Collection
       foreach(string dwarf in SnowWhitesDwarfs)
       {
          System.Console.WriteLine("{0}",dwarf);
       }

    }/* end Main */
    
}/* end class SevenDwarfs */

You can compile the above code using the following command-line:

csc /target:exe /out:SevenDwarfs.exe SevenDwarfs.cs

Here's the output that you get back:

Doc
Dopey
Happy
Grumpy
Sleepy
Sneezy
Bashful

Mapping method parameter keywords in C# to IDL's Directional attributes

There are certain rules that the Interop uses when mapping method parameter keywords in C# such as out, ref to their corresponding directional attributes such as [in], [out], [in,out], [out,retval] and vice versa.

  1. When the method parameter is not qualified by a keyword in C#, it usually gets mapped to the [in] attribute in IDL assuming pass-by-value semantics.
  2. The return value from the C# method is always mapped to the [out, retval] directional attribute in IDL.
  3. The ref method parameter keyword gets mapped to the [in,out] directional attribute in IDL.
  4. The out method parameter keyword gets mapped to an [out] directional attribute in IDL.
  5. Errors that occur in the .NET world are not returned using the return value of the method. But are instead thrown as exceptions.

Read more about error handling here. Here are a few examples of how C# parameter types map to the directional attributes in IDL:

C# Method

IDL Equivalent

Calling semantics in C#

public void Method(String strInput);HRESULT Method([in] BSTR strInput);obj.Method("Hello There");
public String Method();HRESULT Method([out, retval] BSTR* pRetVal);String strOutput = obj.Method();
public String Method(ref String strPassAndModify);HRESULT Method([in, out] BSTR* strPassAndModify, [out, retval] BSTR* pRetVal);String strHello = "Hello There";<BR>String strOutput = obj.Method(ref strHello);
public String Method(out String strReturn);HRESULT Method([out] BSTR* strReturn, [out, retval] BSTR* pRetVal);//Need not initialize strHello<BR>String strHello;<BR>String strOutput = obj.Method(out strHello);
public String Method(String strFirst, out String strSecond, ref String strThird);HRESULT Method([in] BSTR bstrFirst, [out] BSTR* strSecond, [in, out] BSTR* strThird, [out, retval] BSTR* pRetVal);String strFirst = "Hi There";<BR>String strSecond;<BR>String strThird = "Hello World";<BR>String strOutput = obj.Method(strFirst,out strSecond, ref strThird);

Reusing Classic COM Components in Managed code

One of the nice features of the interop is that you can have your managed .NET class use the inheritance or containment models to reuse functionality provided by an existing COM component. The beauty of this that a .NET application consuming a managed .NET component never gets to know that the managed component is internally leveraging unmanaged code implementation from Classic COM components. We'll take a look at some of the ways that a managed .NET component can reuse an existing COM Component:

We call this mixed mode because we have managed classes reusing code and functional logic already available in unmanaged COM components.

Reuse Mechanisms in Classic COM

Classic COM has never subscribed to the idea of implementation inheritance but only played by interface inheritance. The traditional reuse mechanisms in COM have been to use containment and aggregation.

Classic COM Containment

Component reuse through Containment in Classic COM

Just to refresh your memory, Containment allows you to expose an outer component that totally subsumes the inner component within itself. Only the outer component's interface is ever visible to clients. The methods exposed by the outer component's interface usually handle the implementation themselves and/or delegate the work to the inner component when needed. The outer component creates an instance of the inner component and forwards the calls to the inner component when it needs to leverage some of the functionality exposed by the inner component. From the client's perspective, it never knows that there is an inner component shielded by the outer component that does work for the outer component.

Classic COM Aggregation

Component reuse through Aggregation in Classic COM

In the case of Aggregation, the outer object no longer forwards calls to the inner component. Instead, it allows the client to party directly with the inner component by handing over the inner component's interface pointer to it. The outer component no longer gets to intercept method calls on the inner component's interface because the client directly interacts with the inner component. The inner component uses a default IUnknown (Non-delegating unknown) implementation if it is not being aggregated and uses the Outer Component's IUnknown implementation (Controlling/Delegating unknown) if it's being aggregated. This ensures that the client always gets the IUnknown interface pointer of the outer component and never the non-delegating IUnknown interface pointer of the inner component. Again, from the client's perspective, it does not know that there's an inner component. The client thinks that the inner component's interface is just another interface exposed by the outer component.

Let's take a look at the various ways in which you could reuse unmanaged code in existing COM components from .NET classes:

Reuse through Mixed-mode inheritance:

In this reuse model, you can have your managed .NET class extend/inherit from an unmanaged COM coclass. In addition to that, the managed class has the option of overriding methods in the COM coclass' interface or accept the base COM coclass' implementation. This is a very powerful model where you get to mix both managed and unmanaged implementations within the same class.

Mixed-mode inheritance

Inheriting unmanaged code from COM Components in managed classes

Let's take a look at the code snippet below to understand this a little better. We'll create a COM Object called Flyer using ATL that supports an interface called IFlyer with two methods, TakeOff() and Fly(). Here's the IDL declaration for the component:

[
 ....
]
interface IFlyer : IDispatch
{
    [id(1), helpstring("method TakeOff")] 
            HRESULT TakeOff([out,retval] BSTR* bstrTakeOffStatus);
    [id(2), helpstring("method Fly")] 
            HRESULT Fly([out,retval] BSTR* bstrFlightStatus);
};

[
  .....
]
coclass Flyer
{
   [default] interface IFlyer;
};

Here's the implementation of the two methods:

STDMETHODIMP CFlyer::TakeOff(BSTR *bstrTakeOffStatus)
{
   *bstrTakeOffStatus = 
       _bstr_t(_T("CFlyer::TakeOff - This is COM taking off")).copy();
   return S_OK;
}

STDMETHODIMP CFlyer::Fly(BSTR *bstrFlyStatus)
{
    *bstrFlyStatus = 
        _bstr_t(_T("CFlyer::Fly - This is COM in the skies")).copy();
    return S_OK;
}

Before this component can be consumed by managed code, you'll have to generate the metadata proxy for this component from its typelibrary. To do that, you need to issue the following command from the command-line:

tlbimp MyFlyer.tlb /out:MyFlyerMetadata.dll

We'll now create managed classes that inherit from this component using the usual inheritance semantics, so that the component's functionality can be reused. One of the managed classes, Bird, inherits from the metadata type corresponding to the Flyer COM object. This means that it would inherit all the methods of the Flyer COM component. The other managed class, Airplane, overrides the TakeOff and Fly methods with its own implementation. One caveat here is that you cannot selectively override only specific methods from the COM coclass in your managed code. If you decide to override a single method in the COM coclass in your managed code, you'd have to override the rest of the methods as well. In other words, you cannot provide an overridden implementation just for the TakeOff method and implicitly have the managed class use the Fly implementation from the COM object. You would need to override the Fly method as well in the managed class and provide an implementation for it. If you need to reuse the COM coclass' implementation, you could call base.Fly from the managed class' Fly implementation. You might get away with selectively overriding specific methods during compile time. But at runtime, you'd end up running into a System.TypeLoadException exception with an error message that reads something like: 'Types extending from COM objects should override all methods of an interface implemented by the base COM class'.

C#
using System;
using MyFlyerMetadata;

// Inherit from the metadata type representing
// the unmanaged COM component
// Use the COM Component's implementation 
// of TakeOff & Fly (Use the base class' implementation)
public class Bird : Flyer 
{

}/* end class Bird */

// Inherit from the metadata type representing
// the unmanaged COM component
// Override the COM object's method implementations in our
// derived managed-class.
// (Also call base class implementation when
// necessary using base.MethodName())
public class Airplane : Flyer
{

    // Override the COM Component's Flyer::TakeOff implementation
    // with our own implementation
    public override String TakeOff() {
    
       return "Airplane::TakeOff - This is .NET taking off";
       
    }/* end TakeOff */

    // Override the COM Component's Flyer::Fly implementation
    // with our own implementation
    public override String Fly() {
    
       // Can call the base class' implementation too if you
       // wish to.
       System.Console.WriteLine(base.Fly());
       return "Airplane::Fly - This is .NET in the skies";
       
    }/* end Fly */

}/* end class Airplane */

public class FlightController
{
    public static void Main(String[] args)
    {
        Bird falcon = new Bird();
        System.Console.WriteLine("BIRD: CLEARED TO TAKE OFF");
        System.Console.WriteLine(falcon.TakeOff());
        System.Console.WriteLine(falcon.Fly());

        Airplane skyliner = new Airplane();
        System.Console.WriteLine("AIRPLANE: CLEARED TO TAKE OFF");
        System.Console.WriteLine(skyliner.TakeOff());
        System.Console.WriteLine(skyliner.Fly());

    }/* end Main */

}/* end FlightController */

You can compile the above program using the following command in the DOS command-line:

csc /target:exe /out:FlightClient.exe /r:MyFlyerMetadata.dll FlightClient.cs

Running the program above, gives you the following output:

BIRD: CLEARED TO TAKE OFF
CFlyer::TakeOff - This is COM taking off
CFlyer::Fly - This is COM in the skies
AIRPLANE: CLEARED TO TAKE OFF
Airplane::TakeOff - This is .NET taking off
CFlyer::Fly - This is COM in the skies
Airplane::Fly - This is .NET in the skies

Consumers of the Bird and Airplane managed classes are shielded from having to know that these classes are actually reusing existing COM components via inheritance. Whenever necessary, the managed class overrides all the methods in the COM component with its own implementation. This reuse model, where managed code inherits from unmanaged code is called the Mixed-mode inheritance reuse model.

Reuse through Mixed-mode Containment

In this model, the managed class uses the same principles of containment in Classic COM. All it does is, stash away an instance of metadata proxy class representing the unmanaged COM component as a member. Whenever it requires the services of the COM component, it forwards a request to the component's methods.

Mixed-mode Containment

Reusing unmanaged COM code through Containment/Composition

The managed class has the ability to inject its own code before and after the call to the contained COM component. Here's an example:

C#
using System;
using MyFlyerMetadata;

 .....
 
// Contains an instance of the metadata type representing
// the unmanaged COM Component
public class HangGlider
{
    private Flyer flyer = new Flyer();

    // Forward the call to the contained class' implementation
    public String TakeOff() 
    {
      return flyer.TakeOff();
    }

    // Forward the call to the contained class' implementation
    public String Fly() 
    {
       // Do what you need to do before or after fowarding the 
       // call to flyer
       System.Console.WriteLine("In HangGlider::Fly - " + 
                                "Before delegating to flyer.Fly");
       return flyer.Fly();
    }

}/* end class HangGlider */

public class FlightController
{
    public static void Main(String[] args)
    {
        ....

        HangGlider glider = new HangGlider();
        System.Console.WriteLine("HANGGLIDER: CLEARED TO TAKEOFF");
        System.Console.WriteLine(glider.TakeOff());
        System.Console.WriteLine(glider.Fly());
        
    }/* end Main */

}/* end FlightController */

Here's the output of the above program:

HANGGLIDER: CLEARED TO TAKEOFF
CFlyer::TakeOff - This is COM taking off
In HangGlider::Fly - Before delegating to flyer.Fly
CFlyer::Fly - This is COM in the skies

In the example above, the HangGlider class creates an instance of the Flyer COM component and stores it away as a private member. Whenever a method call arrives that requires the Flyer component's services, it calls into the component using the private instance that it had stashed away earlier. Also, the HangGlider class has the liberty to inject code before and after a call is delegated to the Flyer component's methods. This would not be possible with Mixed-mode inheritance reuse model unless you override all the base class COM methods in your managed class.

Understanding COM Threading models & Apartments from a .NET application's perspective

I remember that when I first started programming with COM, I had not yet ventured into the murky waters of COM Threading models and apartments and had little knowledge of what they really were. I thought it was cool that my object was free threaded and simply assumed that it would be the best performing threading model. Little did I realize, what was happening under the covers. I never knew the performance penalties that would be incurred when an STA client thread created my MTA object. Also, since my object was not thread safe, I never knew I would be in trouble when concurrent threads accessed my object. Truly at that time, ignorance of COM threading models was bliss. Well, that bliss was only ephemeral and my server started crashing unexpectedly. It was then that I was forced to get my feet wet in the COM Threading model waters and learn how each of those models behaved, how COM managed apartments, and what were the performance implications that arose when calling between two incompatible Apartments. As you know, before a thread can call into a COM object, it has to declare its affiliation to an apartment by declaring whether it will enter an STA or MTA. STA client threads call CoInitialize(NULL) or CoInitializeEx(0, COINIT_APARTMENTTHREADED) to enter an STA apartment and MTA threads call CoInitializeEx(0, COINIT_MULTITHREADED) to enter an MTA. Similarly, in the .NET managed world, you have the option of allowing the calling thread in the managed space declare its apartment affinity. By default, the calling thread in a managed application chooses to live in a MTA. It's as if the calling thread initialized itself with CoInitializeEx(0, COINIT_MULTITHREADED). But think about the overhead and the performance penalties that would be incurred if it were calling a classic STA COM component that was designed to be apartment threaded. The incompatible apartments would incur the overhead of an additional proxy/stub pair and this is certainly a performance penalty. You can override the default choice of Apartment for a managed thread in a .NET application by using the ApartmentState property of the System.Threading.Thread class. The ApartmentState property takes one of the following enumeration values: MTA, STA and Unknown. The ApartmentState.Unknown is equivalent to the default MTA behavior. You will need to specify the ApartmentState for the calling thread before you make any calls to the COM object. It's not possible to change the ApartmentState once the COM object has been created. Therefore, it makes sense to set the thread's ApartmentState as early as possible in your code.

C#
// Set the client thread's ApartmentState to enter an STA
Thread.CurrentThread.ApartmentState = ApartmentSTate.STA;

// Create our COM object through the Interop
MySTA objSTA = new MySTA();
objSTA.MyMethod()

As an alternative method, you can tag your managed client's Main entry point method with the STAThreadAttribute or the MTAThreadAttribute to start it up with the desired threading affiliation for consuming COM components. For example, take a look at the code snippet below:

C#
public class HelloThreadingModelApp {

   .....

   [STAThread]
   static public void Main(String[] args) {
  
        System.Console.WriteLine("The apartment state is: {0}", 
                   Thread.CurrentThread.ApartmentState.ToString());
   
   }/* end Main */

}/* end class */

The output that you'd get would be something like:

The apartment state is: STA

If the MTAThread attribute is set, then the ApartmentState would be set to MTA. If no thread state attribute is specified in the client's Main entry point or if the ApartmentState property is not set for the thread from which the COM component is created, then the ApartmentState would be Unknown, which defaults to MTA behavior.

Part II: Consuming .NET Components from COM aware clients

In this section, we'll see how we can consume managed .NET components from unmanaged COM aware clients. Limiting clients for .NET Components to only managed clients would've been a tough pill to swallow for most developers who have spent a great deal of time over the years developing applications that can't be ported overnight to managed code. The .NET framework allows disparate applications in different platforms to talk to managed applications using wire protocols like SOAP. Unmanaged COM aware clients still get easier ways to talk to managed components. The .NET runtime allows unmanaged COM aware clients to seamlessly access .NET Components through the COM Interop and through the tools provided by the framework. This ensures that COM aware clients can talk to .NET components, as if they were talking to plain-vanilla Classic COM Components.

Creating a .NET Component

To begin with, let's put together a simple .NET Component that allows you to look up the temperature at your city. Only classes that are public are added to the typelibrary and exposed to COM aware clients. Also, if the class needs to be creatable from a COM aware client, it needs to have a public default constructor. A public class that does not have a public default constructor still appears in the typelibrary, although it cannot be directly co-creatable from COM. The Temperature Component has two methods DisplayCurrentTemperature and GetWeatherIndications. It has a public read-write property called Temperature defined with the corresponding get/set methods.

C#
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public enum WeatherIndications 
{
   Sunny = 0,
   Cloudy,
   Rainy,
   Snowy
}

[ClassInterface(ClassInterfaceType.AutoDual)]
public class TemperatureComponent
{
   private float m_fTemperature = 0;
   
   // Public Constructor
   public TemperatureComponent()
   {
      m_fTemperature = 30.0f;
   }

   //Public Property Accessors (Defines get/set methods)
   public float Temperature
   {
      get { return m_fTemperature; }
       
      set { m_fTemperature = value;}
      
   }/* end Temperature get/set property */
   

   // Public Method that displays the Current Temperature
   public void DisplayCurrentTemperature() 
   {
      String strTemp = String.Format("The current " + 
                       "temperature at Marlinspike is : " + 
                       "{0:####} degrees fahrenheit", 
                       m_fTemperature);
                
      MessageBox.Show(strTemp,"Today's Temperature");
      
   }/* end DisplayCurrentTemperature */

   // Another public method that returns an enumerated type
   public WeatherIndications GetWeatherIndications() 
   {
      if(m_fTemperature > 70) {
      
        return WeatherIndications.Sunny;
      }
      else {
      
        // Let's keep this simple and just return Cloudy
        return WeatherIndications.Cloudy;
      }
      
   }/* end GetWeatherIndications */
   
   
}/* end class Temperature */

You will also notice that there is an attribute called ClassInterface that is tagged to the Temperature class with its value set to ClassInterfaceType.AutoDual. We'll see the significance of applying this attribute in the section, Snooping in on the generated Typelibrary. For now, think of this as a way to tell typelibrary generation tools like REGASM.EXE and TLBEXP.EXE to export the public members of the .NET Component's class into a default Class Interface in the generated typelibrary. Also remember that using a Class Interface to expose the public methods of a .NET class is not generally recommended because it's creedless to COM versioning. We'll take a look at how we can use interfaces explicitly to achieve the same thing. Defining an interface explicitly, deriving your .NET Component class from this interface and then implementing the interface's methods in your .NET Component is the recommended way of doing things if you are going to expose your .NET Component to COM aware clients. We will compare and constrast these two approaches in detail and also see why the former approach is not recommended, in the section, Snooping in on the generated Typelibrary.

If you are using Visual Studio.NET, you can create a Visual C# project and use the Class Library template to code the above component. If you are a command-line jockey, then here's the command to build the component. This creates an assembly called Temperature.dll.

csc /target:library /r:System.Windows.Forms.dll 
    /out:Temperature.dll TemperatureComponent.cs

Generating a typelibrary from the assembly & Registering the assembly

What you just generated now is a .NET assembly that a COM aware client like Visual Basic 6.0 is clueless about. You need to get some COM friendly type information from it so that our VB client will be happy to party around with it. Earlier, you used a tool called TLBIMP (Type Library Importer) to create a .NET metadata proxy from a COM typelibrary. You need to do the reverse of that here. You need to take in a .NET assembly and generate a typelibrary out of it so that it's usable from a COM aware client. The .NET framework provides a couple of tools for this. You can use the Type Library Exporter utility (TLBEXP.exe) or the Assembly Registration Utility (Regasm.exe), both of which you'll find in the Bin directory of your .NET SDK installation. REGASM is a superset of the TLBEXP utility in that it also does much more than generating a typelibrary. It's also used to register the assembly, so that the appropriate registry entries are made to facilitate the COM runtime and the .NET runtime to hook up the COM aware client to the .NET component. We'll use REGASM.EXE here because we can get both the assembly registration and the typelibrary generation done in one go. But you could use TLBEXP as well to generate the typelibrary, and then use REGASM to register the assembly.

regasm Temperature.dll /tlb:Temperature.tlb

The above call to REGASM.EXE makes the appropriate registry entries and also generates a typelibrary (Temperature.tlb) from the .NET assembly so that the typelibrary can be referenced from our VB 6 client application.

Consuming the component from a VB 6.0 Client

Let's quickly put together a VB Form based application that creates and invokes the .NET Component whose assembly we registered and generated a typelibrary from. Creation of the component is the same as how you would create a COM object. You could either reference the typelibrary and early-bind to the Component or perform a CreateObject call using the component's ProgID to late-bind to the component. Usually, the ProgID generated is the fully qualified name of the class. In our case, the ProgID generated would be TemperatureComponent. But you could use the ProgIDAttribute to specify an user-defined ProgID to override the default ProgID that is generated.

VB
Private Sub MyButton_Click()

  On Error GoTo ErrHandler

  ' Create an instance of the temperature component
  Dim objTemperature As New TemperatureComponent

  ' Display the current temperature
  objTemperature.DisplayCurrentTemperature

  ' Set the temperature property
  objTemperature.Temperature = 52.7

  ' Display the current temperature after property mutation
  objTemperature.DisplayCurrentTemperature
  
  ' Check the weather indications
  If (objTemperature.GetWeatherIndications() = _
                     WeatherIndications_Sunny) Then
     MsgBox "Off to the beach"
  Else
     MsgBox "Stay at home and watch Godzilla on TV"
  End If

  Exit Sub
  
ErrHandler:

  MsgBox "Error Message : " & Err.Description, _
           vbOKOnly, "Error Code " & CStr(Err.Number)
End Sub

To enable the .NET Assembly Resolver to find the assembly housing your component, you will either need to place the component in the same directory as the application that's consuming it, or deploy the assembly as a Shared Assembly in the Global Assembly Cache (GAC). For now, just copy the Temperature.dll to the same directory as your VB Client Application executable. If VB can use the usual Classic COM based invocation mechanism and still get away with invoking and consuming the .NET component, there's got to be a good Samaritan sitting in between the VB6 Client and the .NET component and wiring the COM invocation requests to the actual .NET component. We'll soon see, what happens under the covers.

Shedding more light on the COM Interop wizardry

Let's take a peek at the registry entries that Regasm.exe made when we registered our assembly.

Registry Entries made during Assembly registration by REGASM

Registry Entries made during Assembly registration by REGASM

You can check your Component's CLSID in OLEVIEW.EXE by opening the typelibrary generated by REGASM. Check for the uuid attribute under the coclass section. If you navigate to your HKCR\CLSID\{...Component's CLSID...} key in the registry, you can see REGASM has made the relevant registry entries required by COM to activate an object hosted by an Inproc server. In addition, it has created a few other registry entries such as Class, Assembly, and RuntimeVersion that are used by the .NET runtime. The Inproc Server handler (indicated by the InProcServer32 key's default value) is set to mscoree.dll, which is the core CLR runtime execution engine. The COM runtime calls the DllGetClassObject entry point in MSCOREE.dll (The CLR runtime). The runtime then uses the Class ID (CLSID) passed to DllGetClassObject to look up the Assembly and Class keys under the InProcServer32 key to load and resolve the .NET assembly that will service this request. The runtime also dynamically creates a COM Callable Wrapper (CCW) proxy (a mirror image of the RCW) to handle the interaction between unmanaged code and the managed components. This makes COM aware clients think that they are interacting with Classic COM components and makes .NET Components think that they are receiving requests from a managed application. There is one CCW created per .NET component instance.

Under the hood: Accessing .NET Components from COM aware clients

Under the hood: Accessing .NET Components from COM aware clients

As the saying goes, 'A picture is worth a thousand words', so let the illustration here, do most of the talking as to what is happening under the covers when a COM aware client interacts with a .NET component. The primary players here are the CLR runtime and the COM Callable Wrapper (CCW) that gets fabricated by the .NET runtime. From then on, the CCW takes over and handles most of the spadework to get the two to work together. The CCW handles the lifetime management issues here. COM clients in the unmanaged realm maintain reference counts on the CCW proxy rather than on the actual .NET component. The CCW only holds a reference to the .NET component. The .NET Component lives by the rules of the CLR garbage collector as any other managed type would. The CCW lives in the unmanaged heap and is torn down when the COM aware clients no longer have any outstanding references to the objects. Just like the RCW, the CCW is also responsible for marshaling the method call parameters that move back and forth between the unmanaged client and managed .NET components. It's also responsible for synthesizing v-tables on demand. V-tables for specific interfaces are generated dynamically. They are built lazily only when the COM aware client actually requests a specific interface via a QueryInterface call. Calls on the CCW proxy are eventually routed away to a stub that actually makes the call into the managed object.

Snooping in on the generated Typelibrary

Let's take a quick look at the kind of information that was put into the typelibrary generated by the Assembly Registration utility (REGASM). Open the typelibrary through OLEVIEW's Type Library Viewer so that you can take a look at the IDL file that was reverse engineered from the typelibrary.

// Generated .IDL file (by the OLE/COM Object Viewer)
// typelib filename: Temperature.tlb

[
  uuid(A9F20157-FDFE-36D6-90C3-BFCD3C8C8442),
  version(1.0)
]
library Temperature
{
    // TLib : Common Language Runtime Library :
    // {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
    importlib("mscorlib.tlb");
    // TLib : OLE Automation :
    // {00020430-0000-0000-C000-000000000046}
    importlib("STDOLE2.TLB");

    // Forward declare all types defined in this typelib
    interface _TemperatureComponent;

    typedef [uuid(0820402E-B8B6-330F-8D56-FF079E5B4659), version(1.0),
    custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "WeatherIndications")]    
    enum {
        WeatherIndications_Sunny = 0,
        WeatherIndications_Cloudy = 1,
        WeatherIndications_Rainy = 2,
        WeatherIndications_Snowy = 3
    } WeatherIndications;

    [
      uuid(01FAD74C-3DC4-3DE0-86A9-8490FAEE8964),
      version(1.0),
      custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, 
             "TemperatureComponent")
    ]
    coclass TemperatureComponent {
        [default] interface _TemperatureComponent;
        interface _Object;
    };

    [
      odl,
      uuid(C51D54FA-7C81-35A5-9998-3963EAB4AA12),
      hidden,
      dual,
      nonextensible,
      oleautomation,
      custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, 
             "TemperatureComponent")    
    ]
    interface _TemperatureComponent : IDispatch {
        [id(00000000), propget]
        HRESULT ToString([out, retval] BSTR* pRetVal);
        [id(0x60020001)] 
        HRESULT Equals([in] VARIANT obj, 
                       [out, retval] VARIANT_BOOL* pRetVal);
        [id(0x60020002)]
        HRESULT GetHashCode([out, retval] long* pRetVal);
        [id(0x60020003)]
        HRESULT GetType([out, retval] _Type** pRetVal);
        [id(0x60020004), propget]
        HRESULT Temperature([out, retval] single* pRetVal);
        [id(0x60020004), propput]
        HRESULT Temperature([in] single pRetVal);
        [id(0x60020006)]
        HRESULT DisplayCurrentTemperature();
        [id(0x60020007)]
        HRESULT GetWeatherIndications([out, retval] 
                WeatherIndications* pRetVal);
    };
};

If you take a look at the coclass section, it specifies the default interface as the Class Name prefixed by an _ (underscore) character. This interface is called the Class Interface and its methods comprise all the non-static public methods, fields, and properties of the class. The Class Interface gets generated because you tagged your .NET class with the ClassInterface attribute. This attribute tells typelibrary generation tools such as RegAsm.exe and TlbExp.exe to generate a default interface known as the Class Interface and add all the public methods, fields, and properties of the class into it, so that it could be exposed to COM aware clients.

  • If you do not tag the ClassInterface attribute to a .NET Component's class, a default Class Interface is still generated. But in this case, it's an IDispatch based Class Interface that does not include any type information for the methods exposed nor their DISPIDs. This type of Class Interface is only available to late binding clients. This effect is the same as that of applying the ClassInterfaceType.AutoDispatch value to the ClassInterface attribute. The advantage of the AutoDispatch option is that, since the DISPIDs are not cached and not available as a part of the type information, they don't break existing clients when a new version of the component is released since the DISPIDs are obtained at runtime by clients using something like IDispatch::GetIDsOfNames.

Only public methods are visible in the typelibrary and can be used by COM clients. The private members don't make it into the typelibrary and are hidden from COM Clients. The public properties and fields of the class are transformed into IDL propget/propput types. The Temperature property in our example has both set and get accessor defined and so, both the propset and propget are emitted for this property. There is also another interface called _Object that gets added to the coclass. The Class interface also contains 4 other methods. They are:

  • ToString
  • Equals
  • GetHashCode
  • GetType

These methods are added to the default Class Interface because it implicitly inherits from the System.Object class. Each one of the methods and properties that's added to the interface gets a DISPID that is generated automatically. You can override this DISPID with a user defined one by using the DispId attribute. You'll notice that the ToString method has been assigned a DISPID of 0 to indicate that it is the default method in the class Interface. This means that if you leave out the method name, the ToString method will be invoked.

VB
' Create an instance of the temperature component
Dim objTemperature As New TemperatureComponent

' Invoke the ToString method (the default method)
MsgBox objTemperature

Let's examine the various ways in which we can facilitate the generation of the implicit Class Interface. We'll start by taking a look at the effect of applying the ClassInterfaceType.AutoDual value to the ClassInterface attribute.

C#
[ClassInterface(ClassInterfaceType.AutoDual)]
public class TemperatureComponent
{
   ....
}

Notice that the value (positional parameter value) assigned to the ClassInterface attribute is ClassInterfaceType.AutoDual. This option tells typelibrary generation tools (like RegAsm.exe) to generate the Class Interface as a dual interface and export all the type information (for the methods, properties etc. and their corresponding Dispatch IDs) into the typelibrary. Imagine what would happen if you decided that you want to add another public method to the class. This mutates the Class Interface that gets generated and breaks the fundamental interface immutabilty law in COM because the v-table's structure changes now. Late Bound clients also have their share of woes when they try to consume the component. The Dispatch IDs (DISPIDs) get regenerated because of the addition of the new method and this breaks late bound clients too. As a general rule, using ClassInterfaceType.AutoDual is evil since it is totally agnostic about COM Versioning. Let's take a look at the next possible value that you can set for your ClassInterface attribute. Tagging your ClassInterface attribute with a value of ClassInterfaceType.AutoDispatch forces typelibrary generation tools to avoid generating type information in the typelibrary. So, if you had your TemperatureComponent class tagged with the ClassInterface attribute as shown below:

C#
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class TemperatureComponent
{
  .....
}

then, the corresponding typelibrary generated by RegAsm.exe would have an IDL structure such as this:

// Generated .IDL file (by the OLE/COM Object Viewer)
// typelib filename: Temperature.tlb

[
  uuid(A9F20157-FDFE-36D6-90C3-BFCD3C8C8442),
  version(1.0)
]
library Temperature
{
    ......

    [
      uuid(01FAD74C-3DC4-3DE0-86A9-8490FAEE8964),
      version(1.0),
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, 
               "TemperatureComponent")
    ]
    coclass TemperatureComponent {
        [default] interface IDispatch;
        interface _Object;
    };
};

You will notice that the default interface is an IDispatch interface and neither the DISPIDs nor the method type information is present in the Typelibrary. This leaves the COM aware client to consume .NET Components using only late-binding. Also, since the DISPID details are not stored as a part of the type information in the typelibrary, the clients obtain these DISPIDs on demand using something like IDispatch::GetIDsOfNames. This allows clients to use newer versions of the components without breaking existing code. Using ClassInterfaceType.AutoDispatch is much safer than using ClassInterfaceType.AutoDual because it does not break existing client code when newer versions of the component are released, albeit the former allows only late binding. The recommended way of modeling your .NET component to be exposed to COM aware clients is to do away with the Class Interface itself and instead, explicitly factor out the methods you are exposing into a separate interface, and have the .NET component implement that interface. Using a Class Interface is a quick and easy way to get your .NET component exposed to COM aware clients. But it's not the recommended way. Let's try rewriting our TemperatureComponent by factoring out the methods explicitly into an interface and see how the typelibrary generation differs:

C#
// Define the ITemperature interface
public interface ITemperature {

   float Temperature { get; set; }
   void DisplayCurrentTemperature(); 
   WeatherIndications GetWeatherIndications(); 
   
}/* end interface ITemperature */


// (1) Implement the ITemperature interface in the TemperatureComponent class
// (2) Set the ClassInterfaceType for the ClassInterface
// atrribute to ClassInterfaceType.None

[ClassInterface(ClassInterfaceType.None)]
public class TemperatureComponent : ITemperature
{
    ......

    //Implement the methods in your class

    // Property Accessors (Defines get/set methods)
    public float Temperature
    {
      get { return m_fTemperature; }
      set { m_fTemperature = value;}
      
    }/* end Temperature get/set property */

    // Displays the Current Temperature
    public void DisplayCurrentTemperature() {
       .....
    }/* end DisplayCurrentTemperature */

    // Returns an enumerated type indicating weather condition
    public WeatherIndications GetWeatherIndications() {
       ....
    }/* end GetWeatherIndications */
   
}/* end class Temperature */

Here's how the corresponding IDL file looks like for the generated typelibrary. Notice that the TemperatureComponent class' default interface is now the ITemperature interface that was implemented by the class.

// Generated .IDL file (by the OLE/COM Object Viewer)
// typelib filename: Temperature.tlb

[
  uuid(A9F20157-FDFE-36D6-90C3-BFCD3C8C8442),
  version(1.0)
]
library Temperature
{
    ......

    [
      odl,
      uuid(72AA177B-C6B2-3694-B083-4FF535B40AD2),
      version(1.0),
      dual,
      oleautomation,
      custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ITemperature")    
    ]
    interface ITemperature : IDispatch {
        [id(0x60020000), propget]
        HRESULT Temperature([out, retval] single* pRetVal);
        [id(0x60020000), propput]
        HRESULT Temperature([in] single pRetVal);
        [id(0x60020002)]
        HRESULT DisplayCurrentTemperature();
        [id(0x60020003)]
        HRESULT GetWeatherIndications([out, retval] 
                WeatherIndications* pRetVal);
    };

    [
      uuid(01FAD74C-3DC4-3DE0-86A9-8490FAEE8964),
      version(1.0),
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, 
               "TemperatureComponent")
    ]
    coclass TemperatureComponent {
        interface _Object;
        [default] interface ITemperature;
    };
};

This approach of factoring out the methods of the .NET class explicitly into an interface and having the class derive from the interface and implement it, is the recommended way for exposing your .NET Components to COM aware clients. The ClassInterfaceType.None option tells the typelibrary generation tools that you do not require a Class Interface. This ensures that the ITemperature interface is the default interface. Were you to not specify the ClassInterfaceType.None value for the Class Interface attribute, then the Class Interface would have been made the default interface. Here's the gist of what we learnt in this section:

  • Class Interfaces with ClassInterfaceType.AutoDual are COM version agnostic. Try to avoid using them.
  • Class Interfaces with ClassInterfaceType.AutoDispatch do not export type information and DISPIDs to the typelibrary. They are COM versioning friendly. They can be accessed from COM aware clients only through late-binding.
  • Class Interfaces are a hack. Try to avoid them if possible. Use explicit interfaces instead.
  • Use ClassInterfaceType.None to make your explicit interface the default interface.

Another interesting observation is the way REGASM and TLBEXP generate a mangled method name in the IDL by appending a '_' followed by a sequence number when you have overloaded methods in your .NET class or interface that you are exporting to a typelibrary. For example, if you had exposed the following interface in your .NET Component:

C#
public interface MyInterface
{
    String HelloWorld();
    String HelloWorld(int nInput);
}

Then, the IDL corresponding to the typelibrary that REGASM/TLBEXP generated would look like this.

[
  .....
]
interface MyInterface : IDispatch {

    [id(0x60020000)]
    HRESULT HelloWorld([out, retval] BSTR* pRetVal);
    
    [id(0x60020001)]
    HRESULT HelloWorld_2([in] long nInput, 
                         [out,retval] BSTR* pRetVal);    
};

Notice the '_2' appended to the second HelloWorld method to distinguish it from the first one in the IDL file.

Using attributes to tweak the generated typelibrary metadata

Knowing the rules that RegAsm.exe or TlbExp.exe utilities use, to generate the IDL and subsequently, the typelibrary by introspecting a .NET assembly allows you to mould the typelibrary generation to your requirements. You could inject .NET attributes into your assembly that give these utilities the hints they need to alter metadata in the IDL to affect the typelibrary that gets created. For example, you could change the interface type from dual to an IUnknown only based custom interface or a pure dispinterface using the InterfaceTypeAttribute. Here are some of the ways you can inject attributes to qualify the types in your .NET component and modify the generated typelibrary to suit your requirements.

Altering the interface type

By default, the interfaces used by a .NET Class are transformed to dual interfaces in the IDL. This allows the client to get the best of both early binding and late binding. However, there may be occasions when you want the interface to be a pure-dispinterface or a custom IUnknown only based interface. You can override the default type of the interface using the InterfaceTypeAttribute. Take a look at the example below.

C#
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ISnowStorm 
{
 ....
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IHurricane 
{
 ....
}

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ITyphoon 
{
 ....
}

public interface IWeatherStatistics
{
 .....
}

As seen in the above example, the InterfaceType attribute is used to emit metadata information into the interface so that utilities like RegAsm.exe & TlbExp.exe can use this information to generate the appropriate type of interface in the typelibrary. Here's how the resulting IDL would look like:

 [
    odl,
    uuid(1423FBFA-BE13-3766-9729-9C1AAF5DB08A),
    version(1.0),
    dual,
    oleautomation,
    custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ISnowStorm")
]
interface ISnowStorm : IDispatch {
  ....
};

[
    odl,
    uuid(D95E54B8-FABC-3BDA-AA45-AC4EFF49AF92),
    version(1.0),
    oleautomation,
    custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "IHurricane")
]
interface IHurricane : IUnknown {
  ....
};


[
   uuid(676B3B85-7DB8-306D-A1E9-B6AA1008EDF2),
   version(1.0),
   custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ITyphoon")
]
dispinterface ITyphoon {
       properties:
       methods:
};

[
  odl,
  uuid(A1A37136-341A-3631-9275-FC7B0F0DB695),
  version(1.0),
  dual,
  oleautomation,
  custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
         "IWeatherStatistics")
]
interface IWeatherStatistics : IDispatch {
   .....
}

As seen above, setting the InterfaceType attribute to ComInterfaceType.InterfaceIsIUnknown results in the generation of an IUnknown only based custom interface. Setting the InterfaceType attribute to ComInterfaceType.InterfaceIsIDispatch results in the generation of a pure dispatch only dispinterface. Setting the InterfaceType attribute to ComInterfaceType.InterfaceIsDual or ignoring the InterfaceType attribute altogether (such as the IWeatherStatistics interface in the example) results in the emission of a dual interface.

Altering the GUID and the ProgID

GUIDs for types such as classes and interfaces are generated automatically by the REGASM/TLBEXP utilities, when these types are exported into a typelibrary. But, you still have your final say if you need to assign a specific GUID to the interface or class. You can use the Guid Attribute to specify an user defined GUID. You can also affect the way that a ProgID gets generated for a specific class. By default, the RegAsm.exe utility assigns the fully qualified type name of the class for the Progid. You can use the ProgId attribute to assign a user specified ProgID for your coclass.

C#
[  
   GuidAttribute("AD4760A9-6F5C-4435-8844-D0BA7C66AC50"),
   ProgId("WeatherStation.TornadoTracker")
]
public class TornadoTracker  {
  .....
}

Here's how the IDL file looks like after the Guid attribute has been used on the class:

C#
[
  uuid(AD4760A9-6F5C-4435-8844-D0BA7C66AC50),
  version(1.0),
  custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "TornadoTracker")
]
coclass TornadoTracker {
   ....
};

Notice that the uuid attribute (representing the CLSID of the COM class) in the coclass section contains the value of the GUID that we specified. If you check the HCKR\CLSID\{AD4760A9-6F5C-4435-8844-D0BA7C66AC50}\ProgID Registry Key, you should see the value WeatherStation.TornadoTracker representing the class' ProgID.

Hiding Public types from being exposed to COM

You saw earlier, how all the public classes and interfaces of the class were automatically added to the typelibrary so that they can be referenced and used by COM aware clients. But there may be circumstances when you need to prevent certain public interfaces and public classes from being available to COM. You can do this using the ComVisible attribute. Setting this attribute with a value of false prevents the type to which it is applied from appearing in the typelibrary.

C#
[ComVisible(false)]
public interface IWeatherStatistics
{
    float GetLowestTemperatureThisMonth();
    float GetHighestTemperatureThisMonth();
}

In the above example, the IWeatherStatistics interface is not exported as type-information into the typelibrary, when the assembly is exported to the typelibrary. If the IWeatherStatistics type is used by any other type that's exposed to COM, then it's replaced by an IUnknown interface in the typelibrary. For example:

HRESULT SetWeatherStatistics([in] IWeatherStatistics* pWeatherIndications);

becomes:

HRESULT SetWeatherStatistics([in] IUnknown* pUnkWeatherIndications);

There's a caveat here, though. It does not make sense to apply a [ComVisible(true)] to turn on the visibility of private or protected members in the class because they can never be exposed to COM.

The ComVisible attribute can be applied not only to classes and interfaces, but to a whole slew of other types such as assemblies, methods, fields, properties, delegates, structs etc. too. Take a look at this example below, where we selectively hide specific methods in an interface from being exposed to COM.

C#
public interface IHurricaneWatch {

    void AlertWeatherStations(String strDetails);
    
    [ComVisible(false)]
    void PlotCoordinates();
    
    void IssueEvacuationOrders();    
}

The above snippet of code indicates to the typelibrary generator (RegAsm.exe/TlbExp.exe) that we intend to hide the PlotCoordinates method of the IHurricaneWatch interface from appearing in the generated type library. Here's how the IDL looks like, for the typelibrary that gets generated:

[
  ....
]
interface IHurricaneWatch : IDispatch {

   [id(0x60020000)] HRESULT AlertWeatherStations([in] BSTR strDetails);
   [id(0x60020002)] HRESULT IssueEvacuationOrders();
};
Altering the Marshaling behavior for types

When the runtime marshals managed types into the unmanaged world, it follows certain data type conversion rules. For example, a managed type such as a String is always converted to a BSTR. Types such as Strings could have more than one possible representation in the unmanaged world such as array of ANSI characters (LPSTR), an array of UNICODE characters (LPWSTR), or a BSTR. When the target data type in the unmanaged realm presents more than one possible representation of the underlying managed type, then we call such types, Non-isomorphic types. The COM Interop converts non-isomorphic types to a specific default target type (such as a String always converted to a BSTR). Consider the following piece of code in a C# class:

C#
public void SetProductName(String strProductName) { ... }

When the above code is run through the Typelibrary exporter such as TLBEXP or REGASM, what we get back in the IDL corresponding to the generated typelibrary looks like this:

HRESULT SetProductName([in] BSTR strProductName);

However, there may be occasions when you need to provide alternate representations for the types. For example, you might want to convert a managed type such as a String to a null-terminated array of ANSI characters (LPSTR), instead of a BSTR. This is where the MarshalAs attribute helps. You could use the MarshalAs attribute to control how the conversion happens between managed and unmanaged types. So if you'd like to have the managed String converted to a null-terminated ANSI character array as opposed to a BSTR, you would need to apply the MarshalAs attribute to the method parameter as shown below:

C#
public void SetProductName( [MarshalAs(UnmanagedType.LPStr)] 
                            String strProductName) { .... }

The resulting conversion would look something like this in the IDL representation:

HRESULT SetProductName([in] LPSTR strProductName);

Thus, the MarshalAs attribute can be very useful when you need to tweak data representation mappings between types in the managed and unmanaged worlds.

Exception Handling - .NET Exceptions compared with COM HRESULTs

Let's take a look at how the exceptions raised by .NET components get mapped to COM based HRESULTs. You'll notice that the return types from .NET component methods are converted into an [out,retval] IDL type when run through a typelibrary exporter. The actual return type for the method in the IDL is a HRESULT that indicates a success or failure of a method call. A failed HRESULT is usually the result of a system exception or a user-defined exception raised because of failed business logic. If the method call goes through fine with no exception thrown from the .NET component, then the CCW fills in the returned HRESULT with 0 (S_OK). If the method call fails or business logic validation fails, the .NET component is expected to raise an exception. This exception usually has a failure HRESULT assigned to it and an Error description associated with it. The CCW gleans out details such as the Error Code, Error message etc. from the .NET Exception and provides these details in a form that can be consumed by the COM client. It does this by implementing the ISupportErrorInfo interface (to indicate that it supports rich error information) and the IErrorInfo interface (through which it provides all the error information details) or through the EXCEPINFO structure that is passed through an IDispatch::Invoke call (in case the COM aware client was late binding through a dispinterface). The error propagation happens seamlessly such that .NET exceptions are mapped to their equivalent COM counterparts and delivered to the COM Client by the CCW. Let's modify the Temperature property's set method to raise a user defined error if the temperature specified is not within acceptable limits. We'll soon see how the VB Client traps this error using the usual COM error handling mechanisms.

C#
public class TemperatureComponent
{
   private float m_fTemperature = 0;

   /* Temperature Property (In Fahrenheit)*/
   public float Temperature
   {
      get 
      {
         return m_fTemperature;

      }/* end get */
      
      set
      {
         if((value < -30) || (value > 150))
         {
            TemperatureException excep = new TemperatureException(
                                "Marlinspike has never experienced" + 
                                " such extreme temperatures. " + 
                                "Please recalibrate your thermometer");
            throw excep;
         }

         m_fTemperature = value;

      }/* end set */
      
   }

}/* end class TemperatureComponent */


class TemperatureException : ApplicationException
{
   public TemperatureException(String message) : base(message)
   {
   }

}/* end class TemperatureException*/

Here's a VB client that tries to set the temperature in Marlinspike to a value that's out-of-bounds of the temperature readings accepted by the method and this triggers an exception from the .NET Component.

VB
Private Sub MyButton_Click()

 On Error GoTo ErrHandler

 ' Create an instance of the temperature component
 Dim objTemperature As New TemperatureComponent

 ' Set the temperature to the boiling point of water
 objTemperature.Temperature = 212

 Exit Sub

ErrHandler:
    MsgBox "Error Message : " & Err.Description, _
           vbOKOnly, "Error Code: " & CStr(Err.Number)

End Sub

In the example above, the VB6 client's ErrorHandler block set through the On Error Goto statement uses VB's global intrinsic Err object to get all the error details that the CCW has mapped from the .NET Exception into a COM specific error object implementing the IErrorInfo interface.

Error returned by the .NET Component trappable by the VB Application

The .NET Component here raises a TemperatureException which extends the ApplicationException class. An ApplicationException is usually thrown to indicate errors that relate to usual run-of-the-mill failure in application business logic. The TemperatureException calls the base class to initialize the error message. Since no explicit HRESULT was specified, a failure HRESULT is generated by the ApplicationException class and is returned back. If you want to take control of the value of the HRESULTs instead of accepting the auto generated HRESULT values by the base class, then you can use the HResult protected member in the ApplicationException class (that is accessible from the TemperatureException class) to specify a specific HRESULT value to be returned. Another way to throw exceptions from your .NET component is to use the ThrowExceptionForHR method of the System.Runtime.InteropServices.Marshal class. This method takes an integer that represents a standard HRESULT parameter. Most of these standard HRESULTs map to .NET exception types and the corresponding .NET exception is thrown. For example, if you were to use:

System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(COR_E_OUTOFMEMORY);

then, an OutOfMemoryException would be thrown.

Handling events from .NET Components in unmanaged Event Sinks

We saw earlier how COM objects in the unmanaged world raise events asynchronously using Connection Points and how these events could be consumed by .NET applications. We'll now take a look at the other way round. We'll get a .NET Component to raise events and then have an unmanaged sink consume these events. A .NET Component should declare events representing delegate instances for each of the methods in its outgoing event interface. When an event is raised, all the delegates in the event's list will be invoked. These delegates reference the notification target's handler and can therefore call into the correct handler function provided by the subscriber. The unmanaged sink goes about subscribing to events as if it were interacting with a COM object that supports outgoing interfaces using connection points. The CCW takes care of mapping both these event handling models so that a COM Client's unmanaged handler could still receive notifications when a managed .NET event occurs.

Creating a .NET Component that sources Events

Let's create a .NET Component that notifies subscribers on inclement weather conditions. The component allows the weather station master to set wind speeds that have been recorded. If the wind speeds exceed a certain limit (300 mph), it senses an impending Tornado and notifies subscribers by firing off the OnTornadoWarning event.

C#
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Diagnostics;

// Outgoing Event Interface which the Sink implements. We'll
// use a dispinterface here so that it remains friendly to
// scripting clients 

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ITornadoWatchEvents 
{
   void OnTornadoWarning(int nWindSpeed);

}/* end interface ITornadoWatchEvents */


// Incoming interface containing the methods
// being exposed to COM aware clients
public interface IWeatherNotify {

  int WindSpeed { get; set; }

}/* end interface IWeatherNotify */


// Delegate representing the OnTornadoWarning method of the 
// outgoing event interface
public delegate void TornadoWarningDelegate(int nWindSpeed);


[ 
  ComSourceInterfaces("ITornadoWatchEvents"),
  ClassInterface(ClassInterfaceType.None)
] public class WeatherNotify : IWeatherNotify
{
    // Indicates Windspeed in Miles Per Hour 
    private int m_nWindSpeed = 20;

    // Define an event associated with the TornadoWarningDelegate 
    public event TornadoWarningDelegate OnTornadoWarning;

    // Constructor
    public WeatherNotify() {
    }

    public int WindSpeed
    {
       get {

          // Return the current Wind speed 
          return m_nWindSpeed;
       }
       
       set {

          // Set the WindSpeed to the new value
          m_nWindSpeed = value;

          // Check if the Wind Speed warrants an event notification 
          if(value >= 300) {
         
            try  {

              // Check if the delegate instance for the event exists
              if(null != OnTornadoWarning) {

                // Twister on the loose. Run for cover !!!. 
                // Fire the event to all the managed/unmanaged sink handlers 
                // that have registered for this event.
                
                OnTornadoWarning(m_nWindSpeed);
                
               }/* end if */

            }/* end try */
            catch(Exception ex) {

               Trace.WriteLine(ex.Message);
            }/* end catch */

          }/* end if */

       }/* end set */

    }/* End WindSpeed Property Accessors */

}/* end class WeatherNotify */

Let's dissect the code a bit and try to understand what's going on. The ITornadoWatchEvents interface is the outgoing interface that unmanaged sinks will need to implement in order to receive event notifications. This interface consists of a single method, OnTornadoWarning that notifies clients of the possibility of an approaching Tornado along with the current wind speed. You'll notice that the outgoing interface ITornadoWatchEvents is tagged with an InterfaceType attribute with the positional parameter ComInterfaceType.InterfaceIsIDispatch. This is because by default, the interface would be exported into the IDL and subsequently into the typelibrary as a dual interface. Scripting clients generally crack up when they try to sink in a dual interface and they're only pure dispinterface friendly. So we inject an InterfaceType attribute with a ComInterfaceType.InterfaceIsIDispatch value to force typelibrary generation tools to generate a pure dispinterface for the outgoing interface. You would need to define a delegate (TornadoWarningDelegate) that matches the exact signature of the OnTornadoWarning method in the outgoing interface. If you had more than one method in the outgoing interface, you need to define matching delegates for each method. We are now done with the definition of the outgoing interface and matching delegates for each of the methods in the outgoing interface. Now comes the most important part. You would need to define events representing each of the delegates that you defined for the methods in the outgoing interface.

C#
public event TornadoWarningDelegate OnTornadoWarning;

The delegate instance that the event represents needs to have the same name as the corresponding method in the outgoing interface. This means that the event representing the TornadoWarningDelegate delegate instance will need to be named OnTornadoWarning. That's all there is to it. You are now ready to send out event notifications. In our example, an event notification is sent out when the WindSpeed is set to a value greater than or equal to 300. You first need to check if the delegate instance for the event exists and then fire-off the OnTornadoWarning event. Since the delegate represented by the event is a multicast delegate, all the COM sinks and subscribers in the delegates invocation list will be notified that there's a twister looming on the horizon. You can build the above .NET component using the following command:

csc /target:library /r:System.dll /out:WeatherNotify.dll WeatherNotify.cs

You can now run the assembly through RegAsm.exe to register it and generate a typelibrary out of it. This typelibrary can then be referenced in your VB6 Client that will sink the events.

regasm WeatherNotify.dll /tlb:WeatherNotify.tlb
Handling the events in a VB6 Client Application

Here's a VB 6.0 Client that subscribes to OnTornadoWarning event notifications from the WeatherNotify Component. It's a simple Form based application that subscribes to event notifications using the WithEvents keyword. The objWeatherNotify_OnTornadoWarning subroutine receives event notifications from the .NET component when the WindSpeed is set to a value greater than or equal to 300.

VB Weather Station - Event Sink

VB
Dim WithEvents objWeatherNotify As WeatherNotify.WeatherNotify

Private Sub Form_Load()
    ' Create an instance of the WeatherNotify Component
    Set objWeatherNotify = New WeatherNotify.WeatherNotify
End Sub

Private Sub SetWindSpeedButton_Click()

    ' Clear the warning label
    Me.LabelWarning = ""
    
    ' Set the Windspeed property in the WeatherNotify component
    objWeatherNotify.WindSpeed = Me.WindSpeed
    
End Sub

Private Sub objWeatherNotify_OnTornadoWarning(ByVal nWindSpeed As Long)
    
    ' We've received a notification from the WeatherNotify Component
    ' that there could be a Tornado on the prowl
    Me.LabelWarning = "Tornado Warning: Current Wind Speeds : " & _
                    nWindSpeed & " mph"
    
End Sub

Earlier, you saw how the RCW and the metadata helper classes translated the Connection point event handling to Delegate based event handling semantics so that a .NET application could sink events from COM Components. On a similar relation, the CCW here does most of the plumbing to allow unmanaged code to subscribe to event notifications from .NET Components and to deliver these events to the respective handlers in the unmanaged COM land.

Deploying assemblies hosting .NET components

In the examples that you saw earlier, you placed the .NET Components and the COM Metadata Proxy assemblies in the same directory as the application that was consuming it, so that the runtime's Assembly resolver would locate it when it probes for the assembly. Assemblies deployed this way in the same directory of the application referencing it are called Private Assemblies. Yet another way of locating referenced assemblies is by using configuration files to tell the Assembly resolver where to look for the referenced assembly. When the .NET runtime resolves and binds to such private assemblies, it has no concerns for versioning. If your assembly is often used by a large number of applications, then it makes sense to deploy your assembly in a global repository so that they can be shared and used by other applications. This shared assembly repository is called the Global assembly cache (GAC). Assemblies deployed in the GAC are called Shared Name Assemblies or Strong Name Assemblies. This is because these assemblies have a unique "Shared Name" or "Strong Name" associated with them and their identity is uniquely qualified with a textual name, version number, public key token, culture information, and a digital signature. So how do we go about generating a Strong name for an assembly? The first thing you need to do is to generate a Public-Private Key Pair using the Strong Name utility (SN.exe). You can run SN.exe from the command-line to create a new random key pair.

sn -k MyKeyPair.snk

If everything went off well, you should see the following message:

Key pair written to MyKeyPair.snk

Once the key pair is generated, you need to associate this key pair with the Assembly for which you need to generate a shared name. You can do this using the System.Reflection.AssemblyKeyFileAttribute. The AssemblyKeyFile attribute associates the key pair file that was generated from SN.exe with the assembly so that the public key and the digital signature (generated using the private key and the information in the assembly's manifest) can be used to generate the assembly's shared name. The key pair file is generally required during compile time to generate the fully qualified shared-name signature. But for security reasons, most organizations are a little apprehensive about passing their private key over to the developer of the assembly. For this reason, there is a Delay signing option available. The System.Reflection.AssemblyDelaySignAttribute allows you to sign the assembly with the private key at a later time.

Let's make the Temperature.dll .NET assembly that we used earlier as a Strong named assembly. To do that, you need to add the AssemblyKeyFile attribute to your component. If you are using Visual Studio.NET, there is a placeholder for this attribute and other similar global attributes in AssemblyInfo.cs file in your Project Solution. You can edit the file and specify MyKeyPair.snk as the file containing your key pair.

C#
[assembly:AssemblyKeyFile("MyKeyPair.snk")]

You can specify the versioning information for the assembly using the System.Reflection.AssemblyVersionAttribute. This attribute allows you to specify the version of the assembly. The version usually follows the pattern major.minor.build.revision. There is usually a placeholder for this attribute too in AssemblyInfo.cs in your C# Class Library solution in Visual Studio.NET. You can go in there and edit this attribute.

C#
[assembly: AssemblyVersion("1.0.0.0")]

Rebuild the Temparature Component after editing the two attributes specified above in the AssemblyInfo.cs file. Your Assembly now has a strong name associated with it. To verify this, issue the following command using the Strong Name utility to list the assembly's public Key and token.

sn -Tp Temperature.dll

An output similar to this is returned back:

Public key is
0024000004800000940000000602000000240000525341310004000001000100ed87f0432cbf37
fc70eec5d0e59d7e47327729cd99e257a2790c690957691f20c01b47d46a72b20b4f37a829f6ad
82e6594221bbd0193b5499ca0a83db7fc9b78bcb07177f02ef9c827688246f6073f34405e9a441
37017cf6ed52c5001272b0b820926f078bbe8705fa9d411a18d692c94be9541bb3fde38b1b1f79
5a06dde8

Public key token is f80b1601a4d8a9dd

If you use the same command on a private assembly that does not have a strong name associated with it, this is what you get:

sn -Tp WeatherNotify.dll

WeatherNotify.dll does not represent a strongly named assembly

You are now ready to deploy the Temperature.dll in the Global Assembly Cache (GAC), so that the assembly resolver can locate this assembly in the GAC when applications attempt to load or use this assembly. You can list out all the assemblies in the GAC using the following command:

gacutil -l

Alternatively, you can use a Windows shell extension in shfusion.dll that allows you to view, add and remove assemblies in the GAC. If you navigate to the Assembly folder under your Windows Directory, this extension will provide you with a view of the assemblies deployed in the GAC along with properties such as Name, Type, Version, Culture and Public key token. To deploy the Temperature.dll assembly, just type in the following command in the command-line and it should be deployed in the GAC.

gacutil -i Temperature.dll

If everything went off well, you would get a message such as:

Assembly successfully added to the cache

You could also drag and drop your strong name assemblies into the view provided by the shell extension and it should install it for you. Once your assemblies are deployed in the GAC, you are relieved from the rigmarole of having to place your assembly in the application's folder or mess with the configuration files to tell the resolver where to find the assembly. The assembly resolver will now locate the assembly in the GAC.

Shell Extension for manipulating Assemblies in the GAC

Deploying the assembly in the GAC has advantages like side-by-side execution of assemblies with different version numbers, single-instancing of assemblies (also called Code Page Sharing) that allows the runtime to load fewer copies of the assembly when used by multiple applications, decreased load times, and facilitates Quick fix Engineering (QFE) based hot-deployment of assemblies.

Thread Affinity in .NET Components

In the Understanding COM Threading Models and Apartments from a .NET Application's perspective section, you saw how .NET applications could declare the calling thread's apartment affiliation before creating a classic COM Component. Now, let's take a look at the other side of the equation. Particularly, the kind of threading behavior that .NET Components exhibit when they are created from unmanaged COM aware applications. The thread affinity of a .NET Component is defined by the context that the object lives in. A Context is essentially an environment hosted by the AppDomain (a light weight process) in which the object gets created. Each context in turn hosts objects that share common usage requirements such as Thread Affinity, Object pooling, Transaction, JIT Activation, Synchronization etc. These contexts are created as and when required by the runtime depending on the attributes, and the interception services required by the object. If there is an existing context that matches the usage rules that govern the object, then the runtime offers it accommodation in that context. If it does not find a matching context, a new context is created for the object to live in.

With that said, every AppDomain also hosts a Default context. The Default Context in turn hosts Context Agnostic (Context Agile) objects. These objects are not bound to any context. Context Agile objects do not require any attributes, special usage policies and interception services. Take a look at the following table that summarizes how .NET components behave in cross-context access scenarios based on their context agility:

 .NET classes that extend MarshalByRefObject.NET classes that extend ContextBoundObject.NET classes that niether extend from MarshalByRefObject nor ContextBoundObject

Cross-Context calls within the same AppDomain (Intra AppDomain calls)

Context-Agile. Direct Access. (Emulates a Classic COM object that aggregates the Free-threaded Marshaler)

Context-Bound. Object is accessed from any other context only through a proxy.Context-Agile. Direct Access. (Emulates a classic COM object that aggregates the Free-threaded Marshaler)
Cross-Context calls across AppDomains (Inter AppDomain calls)Exhibit Marshal-By-Reference semantics. Object is accessed from any other context, only through a proxy.Exhibit Marshal-By-Reference semantics. Object is accessed from any other context, only through a proxy.

Exhibit Marshal-By-Value semantics. When tagged with the [serializable] attribute, a copy of the object is recreated in the caller AppDomain's context.

Thread neutral behavior when accessed by unmanaged COM aware clients

Take a look at how a .NET component advertises its threading model to COM when an assembly is run through REGASM.EXE in order to make the appropriate registry entries for COM aware clients to look up.

Threading Model as advertised by .NET Components to COM aware clients

The ThreadingModel key under InprocServer32 has a value 'Both'. In Classic COM, objects that advertise their ThreadingModel as 'Both' are willing to move into their caller's apartment, be it STA or MTA. In addition, 'Both' threaded objects that also aggregate the Free-threaded marshaler, give other apartments to which they are marshaled, direct interface pointer references as opposed to proxies. Context Agile .NET components (those that do not extend from ContextBoundObject) are analogous to thread-neutral COM objects that aggregate the free-threaded marshaler. Let's check to see how the .NET component behaves as we pass interface references to the .NET component across COM apartments in the unmanaged client. Take a look at this simple C# class that we'll be exposing to the unmanaged COM client:

C#
using System;
using System.Runtime.InteropServices;
    
public interface IHelloDotNet {

  String GetThreadID();

}/* end interface IHelloDotNet */

[ClassInterface(ClassInterfaceType.None)]    
public class HelloDotNet : IHelloDotNet
{
   public HelloDotNet() {
   }
   
   public String GetThreadID() {

      return AppDomain.GetCurrentThreadId().ToString();
   }

}/* end class HelloDotNet */

The above class implements the GetThreadID method from the IHelloDotNet interface. This method returns the ID of the current thread that's executing in the AppDomain into which this object is loaded. To build the above class into an assembly and to make the appropriate registry entries for COM, issue the following commands from the command-line:

csc /target:library /out:HelloDotNet.dll HelloDotNet.cs
regasm HelloDotNet.dll /tlb:HelloDotNet.tlb

We'll now go on and consume the .NET component from a COM aware client. We'll use a C++ console application that will create the .NET component in its main thread (an STA) and then pass it across to two other apartments (an STA apartment and an MTA apartment) by spawning two worker threads. We'll take a look at what happens when a raw interface pointer to the object is passed across apartments. Then, we'll take a look at what happens when a marshaled reference is passed across apartments by using explicit inter-thread marshaling calls that use the CoMarshalInterface/CoUnmarshalInterface API family. Take a look at the code below (Error checking in the code is omitted for brevity):

.......
 
#import "mscorlib.tlb"
// Import the .NET component's typelibrary
#import "HelloDotNet.tlb" no_namespace

// Worker Thread functions
long WINAPI MySTAThreadFunction(long lParam);
long WINAPI MyMTAThreadFunction(long lParam);

// Use the compiler generated smart pointer wrappers
IHelloDotNetPtr spHelloNET = NULL;

// Stream ptr that will contain the marshaled interface
IStream* g_pStream1 = NULL;
IStream* g_pStream2 = NULL;

int main(int argc, char* argv[])
{
    .......

    // Make the primary thread enter an STA
    ::CoInitialize(NULL);

    // Log the thread id 
    cout << "The Thread ID of the primary STA thread is : " 
         << ::GetCurrentThreadId() << endl;

    // Create the .NET object via the COM Interop
    hr = spHelloNET.CreateInstance(__uuidof(HelloDotNet));

    cout << "From .NET when called from the primary STA Thread : " 
         << spHelloNET->GetThreadID() << endl;

           .......

    // Marshal the interface pointer to a stream so that the
    // worker threads can get back an unmarshaled reference from it.
    hr = CoMarshalInterThreadInterfaceInStream(_uuidof(IHelloDotNet),
                                               spHelloNET,
                                               &g_pStream1);

    hr = CoMarshalInterThreadInterfaceInStream(_uuidof(IHelloDotNet),
                                               spHelloNET,
                                               &g_pStream2);

    // Create a worker thread that enters a STA
    hThreadSTA = CreateThread(NULL,0,
                             (LPTHREAD_START_ROUTINE)MySTAThreadFunction,
                              NULL,0 ,&dwThreadIDSTA);

    // Log the thread id 
    cout << "The Thread ID of the STA based Worker thread is : " 
         << dwThreadIDSTA << endl;

    // Create a worker thread that enters a MTA
    hThreadMTA = CreateThread(NULL,0,
                             (LPTHREAD_START_ROUTINE)MyMTAThreadFunction,
                              NULL,0,&dwThreadIDMTA);

    // Log the thread id 
    cout << "The Thread ID of the MTA based Worker thread is : " 
         << dwThreadIDMTA << endl;

    // Wait for both the worker threads to complete
    ::WaitForSingleObject(hThreadSTA,INFINITE);
    ::WaitForSingleObject(hThreadMTA,INFINITE);

    // Return the status
    return 0;

}/* end main */


/*
 * Worker Thread Function that enters a STA Apartment
 */
long WINAPI MySTAThreadFunction(long lParam)
{
    // Let the thread enter a STA
    ::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);

    // Invoke the method using the raw interface pointer
    cout << "From .NET when called from the STA Worker Thread (Direct Access) : " 
         << spHelloNET->GetThreadID() << endl;

    // Unmarshal the interface pointer from the stream
    IHelloDotNetPtr spHello = NULL;
    HRESULT hr = CoGetInterfaceAndReleaseStream(g_pStream1,
                                    __uuidof(IHelloDotNet),
                                        (void **)&spHello);
                                        
    if(S_OK == hr)
    {
       cout << "From .NET when called from the STA Worker Thread (Marshaled) : " 
            << spHello->GetThreadID() << endl;
    }

    // Exit from the thread
    return 0;

}/* end MySTAThreadFunction */


/*
 * Worker Thread Function that enters a MTA Apartment
 */
long WINAPI MyMTAThreadFunction(long lParam)
{

    // Let the thread enter a MTA
    ::CoInitializeEx(NULL,COINIT_MULTITHREADED);

    // Invoke the method using the raw interface pointer
    cout << "From .NET when called from the MTA Worker Thread (Direct Access) : " 
         << spHelloNET->GetThreadID() << endl;

    // Unmarshal the interface pointer from the stream
    IHelloDotNetPtr spHello = NULL;
    HRESULT hr = CoGetInterfaceAndReleaseStream(g_pStream2,
                                     __uuidof(IHelloDotNet),
                                         (void **)&spHello);

    if(S_OK == hr)
    {
       cout << "From .NET when called from the MTA Worker Thread (Marshaled) : " 
            << spHello->GetThreadID() << endl;
    }

    // Exit from the thread
    return 0;

}/* end MyMTAThreadFunction */

Here's the output that you get back when the console application is run.

The Thread ID of the primary STA thread is : 2220
From .NET when called from the primary STA Thread : 2220
The Thread ID of the STA based Worker thread is : 2292
The Thread ID of the MTA based Worker thread is : 2296
From .NET when called from the STA Worker Thread (Direct Access) : 2292
From .NET when called from the STA Worker Thread (Marshalled) : 2292
From .NET when called from the MTA Worker Thread (Direct Access) : 2296
From .NET when called from the MTA Worker Thread (Marshalled) : 2296

Notice that for all these calls, no thread switch occurs between the thread making the call in the client and the thread invoking the actual method in the .NET component. It other words, the .NET component is Context agile and always executes in the caller's thread. From the code snippet above, observe that the effect of marshaling an object reference (using inter-thread marshaling APIs such as CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream) is the same as that of passing a direct object reference across apartments. Eventually, the receiving apartment gets an apartment-neutral interface pointer that it can use to call into the .NET component. The .NET component exhibits all the behavior that is reminiscent of Both threaded Classic COM Components that aggregate the free-threaded marshaler.

Conclusion: COM's position in the über powerful .NET world

In the first part of this article, we took a look at how you could expose Classic COM components to .NET applications executing under the purview of the Common Language Runtime (CLR). We saw how the COM interop seamlessly allows you to reuse existing COM components from managed code. Then, we skimmed through ways to invoke your COM component using both early binding and late binding along with ways to do runtime type checking and dynamic type discovery. We took a journey through understanding how delegates work in .NET, the role they play in the .NET event-handling model, and how the COM Interop acts as an adaptor to wire the connection points event handling model in classic COM to the delegate based Event handling model in .NET. We discussed about how to expose COM collections to .NET applications and use C#'s foreach syntax to easily iterate through the elements of the collection. Then, we looked at how directional attributes in IDL files get mapped to the corresponding directional parameter types in C#. We also learnt about some of the reuse options available for Classic COM components from .NET applications using inheritance and containment. Finally, we saw how managed threads declare their apartment affiliations when invoking COM components.

In the latter half of this article, we took a dip into exploring how COM aware clients from the pre-.NET era could consume .NET components as if they were classic COM components. We saw how the CCW and the CLR facilitate this to happen seamlessly from a programming perspective. We briefly explored the possibilities of using attributes to emit metadata into .NET types so that the typelibrary generated could be tailored and fine-tuned to your requirements. We looked at how the exception handling mechanisms in the two worlds are correlative. We also discussed about how to go about receiving asynchronous event notifications from .NET components in unmanaged event sinks. Then, we turned our attention to the deployment options available and how to deploy .NET components as Shared assemblies. Lastly, we discussed the thread-neutral behavior of .NET components and saw how Context-agile .NET components are analogous to Classic COM 'Both' threaded components that aggregate the free-threaded marshaler (FTM).

As a COM developer, you might wonder if it makes sense to continue writing COM components or make the transition directly into the .NET world by writing all your components and business logic code wrapped up as managed components using one of the languages such as C#, VB.NET or any of your favorite languages that generates CLR compliant managed code. In my opinion, if you have tons of COM code out there that you just cannot port to managed code overnight, it makes sense to leverage the interop's ability to reuse existing COM components from .NET applications. But, if you are starting with writing new business logic code from scratch, then it's best to wrap your code as managed components using one of the languages that generate CLR managed code. That way, you can do away with the performance penalties that are incurred while transitioning between managed and unmanaged boundaries. Eventually, we COM developers don't have to despair. Our beloved COM components will continue to play well with .NET applications. The tools provided with the .NET framework and the COM interop mechanism provided by the runtime make it seamless from a programming perspective as to whether your .NET application is accessing a Classic COM component or a managed component. So in essence, the marriage between COM and the brave new über powerful .NET world should be a happy one, and the COM that we all know and love so much will still continue to be a quintessential part of our lives.


Quotable Quotes

"Virtually all aspects of the COM programming model have survived (interfaces, classes, attributes, context, and so on). Some may consider COM dead simply because CLR objects don't rely on IUnknown-compliant vptrs/vtbls. I look at the CLR as breathing new life into the programming model that I've spent the last seven years of my life working with, and I know there are other programmers out there who share this sentiment."

- Don Box, in his 'House of COM' column in the MSDN Magazine(Dec, 2000)

"COM lives on in spirit, if not in body !"

- Peter Foreman, in the Developmentor DOTNET mailing list

Bibliography

Acknowledgements

I'd like to express my sincerest gratitude to Tom Archer for encouraging me to write and for including a part of this tutorial in his book Inside C#. I'd also like to thank the wonderful folks at the Developmentor DOTNET mailing list for giving .NET developers, food for thought, everyday.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionConsuming COM component in .Net application without creating object using new operator Pin
Member 1065065024-Mar-14 19:43
Member 1065065024-Mar-14 19:43 
GeneralMy vote of 5 Pin
R. Hoffmann18-Apr-13 22:59
professionalR. Hoffmann18-Apr-13 22:59 
QuestionDuplicated Interface and Class Names Pin
Hassan R. Bhatti17-Sep-12 10:02
Hassan R. Bhatti17-Sep-12 10:02 
QuestionProblem about a class with namespace Pin
jackzhoujun15-May-12 10:49
jackzhoujun15-May-12 10:49 
QuestionVery impressive implanation Pin
TonyCodePrj24-Apr-12 22:54
TonyCodePrj24-Apr-12 22:54 
GeneralMy vote of 5 Pin
mlop3s23-Mar-12 13:18
mlop3s23-Mar-12 13:18 
Questionautomation error -2146233079 80131509 using access 2007 vba to access .net generated web service client proxy Pin
Member 859053623-Jan-12 7:06
Member 859053623-Jan-12 7:06 
GeneralMy vote of 5 Pin
Joe T. King5-Dec-11 3:01
Joe T. King5-Dec-11 3:01 
QuestionHow to access remote COM+ across domains? Pin
zcs93537248-Apr-10 21:34
zcs93537248-Apr-10 21:34 
QuestionWriting to Unmanaged memory Pin
kamarchand8-Oct-09 9:32
kamarchand8-Oct-09 9:32 
GeneralExactly What I Needed Pin
James M Rhodes25-Sep-09 10:31
James M Rhodes25-Sep-09 10:31 
GeneralBrilliant Article but... Pin
kazim bhai8-Sep-09 2:18
kazim bhai8-Sep-09 2:18 
GeneralThank you Pin
logicchild11-Jul-09 22:20
professionallogicchild11-Jul-09 22:20 
GeneralA classic article Pin
Gajab1-Jul-09 20:58
Gajab1-Jul-09 20:58 
QuestionCan I use CCW to implement a pure COM event interface without inheriting from IDispatch? Pin
xiaolin.lan4-Jan-09 22:45
xiaolin.lan4-Jan-09 22:45 
GeneralCOM - .NET Query Pin
jaygaba15-Oct-08 2:39
jaygaba15-Oct-08 2:39 
GeneralRe: COM - .NET Query Pin
Member 1065065024-Mar-14 23:17
Member 1065065024-Mar-14 23:17 
Questionwhere do you place the .dll for COM files and metadata files ? Pin
Patrice PENNEL8-Oct-08 1:04
Patrice PENNEL8-Oct-08 1:04 
GeneralTemperatureComponent Interface is not published Pin
Juan C Calderon13-Sep-08 17:11
Juan C Calderon13-Sep-08 17:11 
GeneralGreat Pin
Michael Chourdakis31-Aug-08 10:31
mvaMichael Chourdakis31-Aug-08 10:31 
GeneralGreat article! [modified] Pin
Siegfried Glaser22-Jun-08 2:04
Siegfried Glaser22-Jun-08 2:04 
QuestionHow to release a COM object created with Activator.CreateInstance? Pin
Crazy Joe Devola15-Apr-08 14:18
Crazy Joe Devola15-Apr-08 14:18 
QuestionCalculations on Input Form Pin
IvanIT23-Feb-08 13:26
IvanIT23-Feb-08 13:26 
QuestionPassing arrays Pin
Jon Lea23-Feb-08 12:48
Jon Lea23-Feb-08 12:48 
AnswerRe: Passing arrays Pin
Member 896238711-May-12 7:14
Member 896238711-May-12 7:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.