In this article, we review the concepts & walk through an example that demonstrates COM interoperability in the .NET framework.
What is the need for Interoperability?
COM components have different internal architecture from .NET components hence they are not innately compatible. Most organizations, that have built their enterprise applications on COM objects for their middle-tier services, cannot write off the investments in these solutions. These legacy components ought to be exploited by managed code in the .NET framework. This is where Interoperability pitches in; it is a Runtime Callable Wrapper (RCW) that translates specific calls from managed clients into COM-specific invocation requests on unmanaged COM components. The method call on RCW will make .NET components believe that they are talking to just another .NET component.
Before we move on to the core concepts, let's have a small primer on COM.
What is COM?
COM stands for Component Object Model, which is a binary specification for software code reuse. It imposes a standard for the interfaces through which client code talks to component classes. The components IUnknown interface helps to maintain a reference count of the number of clients using the component. When this count drops down to zero, the component is unloaded. All components should implement the IUnknown interface. The reference count is maintained through IUnknow::AddRef() & IUnknow::Release() methods, and interface discovery is handled through IUnknow::QueryInterface().
What is Runtime Callable Wrapper?
.NET application communicates with a COM component through a managed wrapper of the component called Runtime Callable Wrapper. It acts as a managed proxy to the unmanaged COM component.
When we make a method call, it goes onto RCW and not the object itself. RCW manages the lifetime management of the COM component.
How is the Component Binding in interoperability?
Binding refers to information on methods, properties, events, etc that the client needs to know about the object. We still can use the good old technique of bindings in .NET interoperability viz., Early & Late bindings.
Early Binding: Clients obtain compile-time type information from the components type library
Late Binding: Clients lack rich type information of the object, it is known at runtime.
How do we implement COM Interoperability?
We can implement it in the following steps.
- Create Runtime Callable Wrapper out of COM component.
- Reference the metadata assembly Dll in the project and use its methods & properties.
Step 1. There are two ways to generate a managed metadata wrapper:
- Using the Type Library Importer utility
- VS.NET IDE
Type Library Importer (tlbimp.exe) is a command line syntax, that converts COM-specific type definitions in a COM type library into equivalent definitions for .NET wrapper assembly. By default, the utility gives wrapper assembly the same name as COM dll.
The above example generates metadata assembly with the name InteropExampleRCW.dll out of the COM component InteropExample.dll using the following syntax at the VS.NET command prompt.
limp InteropExample.dll /output:InteropExampleRCW.dll /verbose
Note. it internally resolves ADODB references in the COM. Throughout the argument, we can specify the desired assembly name.
Type library importer interrogates COM dlls type library and translates the information therein into .NET format. The metadata assembly so generated contains wrapper classes that can be used by any .NET client e.g. C# Windows clients. RCW is created on the fly whenever the component is created & it acts like managed types to COM-specific data types.
VS.NET IDE also helps us generate metadata assembly.
Click on Project -> Add reference ->COM tab
The tab lists registered components on the local machine, selects the desired COM dll, and adds to the list of selected components. VS.NET automatically generates metadata assembly putting the classes provided by that component into a namespace with the same name as COM dll.
What is the structure of the Wrapper Assembly?
For each class imported into wrapper assembly, two wrapper classes are generated. COM-specific information can be viewed using the MSIL Disassembler utility (ildasm .exe) at the VS.NET command prompt.
ildasm InteropExampleRCW.dll
The InteropExample.dll (developed in VB6.0) component has the following public classes.
Authors
Titles
The generated assembly has four classes.
Authors, AuthorsClass
Titles, TitlesClass
First is the interface having the same GUID as the original COM class, second is the concrete class and whose instance is to be created. The concrete class is suffixed with the word Class. The concrete class implements all the interfaces that are supported by the original COM class.
All the generated types are placed under the single namespace InteropExampleRCW
Step 2. Reference the metadata assembly Dll in the project and use its methods & properties.
Create a new C# Windows application project. Drag the following label, textbox, and data grid controls on the form, and name them accordingly, the form will appear as follows.
Import the required assemblies.
using System.Data;
using System.Data.OleDb;
// Include the following code in appropriate click events:
// Search Author button click event
private void btnSearchAuthors_Click(object sender, System.EventArgs e)
{
// Create an instance of AuthorsClass from the wrapper assembly
InteropExampleRCW.AuthorsClass myAuthorRCW = new InteropExampleRCW.AuthorsClass();
DataSet dsAuthorList = new DataSet("Authors");
OleDbDataAdapter daAuthRecs = new OleDbDataAdapter();
ADODB.Recordset rsAuthors = new ADODB.Recordset();
rsAuthors = myAuthorRCW.GetAuthors(txtAuthors.Text.ToString());
// Invoke method from the RCW
daAuthRecs.Fill(dsAuthorList, rsAuthors, "Authors");
dataGridAuthors.SetDataBinding(dsAuthorList, "Authors");
}
// Search Title button click event
private void btnSearchTitle_Click(object sender, System.EventArgs e)
{
InteropExampleRCW.TitlesClass myTitlesRCW = new InteropExampleRCW.TitlesClass();
DataSet dsTitleList = new DataSet("Titles");
OleDbDataAdapter daTitleRecs = new OleDbDataAdapter();
ADODB.Recordset rsTitles = new ADODB.Recordset();
rsTitles = myTitlesRCW.GetTitles(txtTitle.Text.ToString());
daTitleRecs.Fill(dsTitleList, rsTitles, "Titles");
dataGridTitle.SetDataBinding(dsTitleList, "Titles");
}
The output of the run will look like this.
How Do I Release COM objects?
Runtime Callable Wrapper is managed creation itself; hence its lifetime is controlled by Common Language Runtime. The COM component is freed from memory when the garbage collector calls the Finalize() method on RCW. Internally RCW calls Release() of IUknown interface on COM object.
To explicitly remove COM objects from memory invoke the static method on the Marshal class in System.Runtime.InteropServices namespace.
using System.Runtime.InteropServices;
Marshal.ReleaseComObject(myAuthorRCW);