5,699,431 members and growing! (21,582 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » COM / COM+ » COM Interop     Beginner

Understanding Classic COM Interoperability With .NET Applications

By Aravind C

Discusses how existing COM components can be used from managed code.
VC6, VC7, C++Windows, .NET, .NET 1.0, Win2KVS6, Visual Studio, Dev

Posted: 3 Mar 2001
Updated: 24 Jul 2001
Views: 544,505
Bookmarked: 455 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
355 votes for this Article.
Popularity: 11.59 Rating: 4.55 out of 5
2 votes, 1.4%
1
1 vote, 0.7%
2
0 votes, 0.0%
3
5 votes, 3.4%
4
140 votes, 94.6%
5

(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.

.......
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.

.......
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.

// 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:

......

// 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:

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.

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

 ......
 
 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:

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:

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.

[
  ....
]
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.

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.

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.

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";
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
String strHello;
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";
String strSecond;
String strThird = "Hello World";
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'.

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

//