Introduction
In this article, we review the concepts and walk-through an example that demonstrates COM interoperability in .NET framework.
What is the need for Interoperability?
COM components have a different internal architecture from .NET components, hence they are not innately compatible. Most organizations, which have built their enterprise applications on COM objects for their middle tier services, cannot write off the investments on these solutions. These legacy components ought to be exploited by managed code in the .NET framework. This is where Interoperability pitches in; it�s 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 re-use. It imposes a standard for the interfaces through which client code talks to component classes. The component�s 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()
and IUnknow::Release()
methods, and interface discovery is handled through IUnknow::QueryInterface()
.
What is Runtime Callable Wrapper?
A .NET application communicates with a COM component through a managed wrapper of the component called Runtime Callable Wrapper. It acts as 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 a client needs to know about the object. We still can use the good old technique of bindings in .NET interoperability viz., Early and Late bindings.
- Early binding: Clients obtain compile time type information from the component�s 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 a COM component.
- Reference the metadata assembly DLL in the project and use its methods and properties.
Step 1:
There are two ways to generate a managed metadata wrapper:
- Using Type Library Importer utility.
- VS.NET IDE
Type Library Importer (tlbimp.exe) is a command line syntax, which converts COM specific type definition in a COM type library into equivalent definitions for a .NET wrapper assembly. By default, the utility gives the wrapper assembly the same name as the COM DLL.
The above example generates a metadata assembly with the name �InteropExampleRCW.dll� out of the COM component �InteropExample.dll� using the following syntax at VS.NET command prompt:
tlbimp InteropExample.dll /output:InteropExampleRCW.dll /verbose
Note: it internally resolves ADODB references in the COM. Through �out� argument, we can specify the desired assembly name.
Type library importer interrogates the COM DLL�s type library and translates the information therein into .NET format. The metadata assembly so generated contains wrapper classes that can be used in any .NET client, e.g., C# Windows clients. RCW is created on the fly whenever a component is created and it acts like managed types to COM specific data types.
VS.NET IDE also helps us generate a 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 a metadata assembly putting the classes provided by that component into a namespace with the same name as the COM DLL.
How is the structure of a Wrapper Assembly?
For each class imported into a wrapper assembly, two wrapper classes are generated. COM specific information can be viewed using MSIL Disassembler utility (ildasm.exe) at VS.NET command prompt:
ildasm InteropExampleRCW.dll
InteropExample.dll (developed in VB 6.0) component has the following public classes:
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 a concrete class whose instance is to be created. Concrete class is suffixed with the word �Class�. 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 and properties:
Create a new C# Windows application project. Drag the following Label
, TextBox
and DataGrid
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 the appropriate click events:
To use Authors search from AuthorClass
, have code for Search Author button click event as:
private void btnSearchAuthors_Click(object sender, System.EventArgs e)
{
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());
daAuthRecs.Fill(dsAuthorList,rsAuthors,"Authors");
dataGridAuthors.SetDataBinding(dsAuthorList,"Authors");
}
To use Titles search from TitleClass
, code for 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:
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 the COM object.
To explicitly remove COM objects from memory, invoke the static method on Marshal
class in System.Runtime.InteropServices
namespace:
using System.Runtime.InteropServices;
Marshal.ReleaseComObject(myAuthorRCW);