|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
(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
IntroductionAfter 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 ApplicationsGetting 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 ..... 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 .......
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 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 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 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
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 ApplicationNow that we have generated the metadata that is required by a .NET client, let's try invoking the ....... 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 Please refer to the Exception Handling section in this article, to learn more about how .NET exceptions are mapped to COM Accessing other supported interfaces and Dynamic Type DiscoveryHow does the classic 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 ' .......
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 ObjectsAll 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 .......
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 .NETThe 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
Creating an ATL COM Component that sources eventsLet'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.
We'll create an ATL EXE project that hosts an object called .....
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 .......
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 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 Event handling using DelegatesIf 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 ......
// 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 public delegate void EventHandler(object sender, EventArgs e);
The In our example, whenever the Sinking Unmanaged COM Events in a .NET Application
Here's a simple VB Client application that assumes the role of the Control Tower at John Doe International Airport and calls the 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 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
......
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 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 Using COM Collections from .NET ApplicationsUsing COM based Collections allows you to categorize objects together as a part of the group that exhibit similar behavior. For example, a Creating a COM collection Component using ATLFor 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 ....... //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 .......
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 VBFor the benefit of the VB folks, here's an equivalent implementation of the 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 ApplicationTo 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
Metadata proxy generated by TLBIMP for the IceCreamMenu collection component The
Consuming COM collections in .NET Applications Since the 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
Mapping method parameter keywords in C# to IDL's Directional attributesThere 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
Read more about error handling here. Here are a few examples of how C# parameter types map to the directional attributes in IDL:
Reusing Classic COM Components in Managed codeOne 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.
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.
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 [ .... ] 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, 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() {
// | |||||||||||||||||||||||||||||||||||||||||||||||||