|
Understanding COM Interop
|
|
.NET is a great platform for developing applications and many pieces of code that took lots of effort to write, is now made much simpler. As much as all of us would like to adopt .NET immediately, many organizations cannot. Lots of investment has been made developing applications using technologies like COM and these code bases cannot be junked overnight. Also, .NET may not be suitable for all types of projects. There are still some applications that are best left to low-level languages. Given this need for organizations to preserve existing
code, one of the important goals of .NET during its development was to promote
interoperability with existing technologies. .NET interoperability comes into
three flavors:
Each of these models have different requirements and best practices and this article we will discuss about COM interop. More specifically, we will see how to write a COM component and then invoke it from .NET. We will also see some best practices for developing well behaved COM interop solutions. One important thing to understand is that components
written in .NET are managed whereas components written using COM are unmanaged.
Therefore, the first challenge to overcome is, to bridge these two models. Each
model has its own way of memory allocation, object lifetime management and
parameter passing convention that to bridge these two models require the usage
of an intermediary that handles all these differences. Having
an intermediary is important because we do not want applications writing this
code for each application that wants to interop with COM components. The .NET
developers were aware of this and thus gave an intermediary called as the Runtime
Callable Wrapper (RCW). The CCW takes care of all the intricacies
of communicating between the two platforms. The following figure shows the role
of the CCW.
![]() The main job of the RCW is to hide all the differences
between the two worlds and as such is an object that is created from the
managed heap. Only one instance of the RCW is ever created, irrespective of how
many .NET clients access it. Once a .NET client object wants to instantiate a
COM client, the RCW intervenes and creates the object on your behalf and then
manages the lifetime of the COM object. COM objects are reference counted. This
means that each client accessesing the COM client will increase its reference
count by 1 and each release of the reference decreases its reference count by
1. When the reference count becomes 0, the COM client is released. This
counting happens by calling the Add and Release
methods of the IUnknown interface, an interface that all COM
objects may implement. All these intricacies are taken over by the RCW. It is
important to remember that .NET is a garbage collected environment. So, when
will the RCW be collected?? The RCW will be collected when the last client
holding a reference to the COM object releases the reference. At this point,
the reference count on the COM object becomes 0 and will thus be collected by
the operating system while the RCW will be garbage collected by the .NET
runtime.
COM components also report errors using HRESULTs. The RCW
maps the various HRESULTs into .NET exceptions that can then be captured in
your application.
Creating an RCW
Having seen the basics, the next step is to understand how
the RCW is itself created. There are two methods for creating the RCW.
In this article, we will see an example of both these methods. As an example to illustrate the creation of the RCW, let us consider a simple example of a COM component that just echos back a string that is provided as input. We will see the development of this COM component, followed by the generation of the RCW. We will be using Visual Basic 6.0 to create the COM components although you could use any language of your choice that can generate COM components. You will need to adapt the example suitably to suit the needs of your language. Let us first create the COM component. To do this, open Visual Basic 6.0 and choose to create an ActiveX DLL project. Name the project as HelloWorld and the default class as CHelloWorld. In this class we will create a function called sayHello. Here is the code for the function:
After you have created the function, choose File > Make
DLL to create the DLL file. Since we have named our project as HelloWorld,
the DLL will be called HelloWorld.DLL. Once you have created
the COM DLL, the next step is to create the .NET code that will call into this
DLL. As mentioned before, a .NET code will access the COM component via an RCW
and there are two ways you can create the RCW. The first method involves using
the Visual Studio .NET references dialog box and is the most simplest of the
methods. To use this method, open Visual Studio .NET and choose File > New
> Project > Visual Basic Projects > Console Application. This will
create a console application project in the location of your choice. Once the
project has been created, in the Solution Explorer, right-click the references
node and choose Add New Reference. In the Add Reference dialog box, choose the
COM tab and locate the HelloWorld DLL that we created. The following figure
shows the located DLL.
![]() Once you have created a reference to the HelloWorld DLL,
you will now be able to access the DLL. But before that, what did Visual Studio
.NET do?? If you check out the bin folder of your .NET console
application, there will be a file called Interop.HelloWorld.dll
file that just appeared there!! This is the RCW and Visual Studio .NET
automatically created it for you. You can open this assembly in ILDASM to see
what it has. The following figure shows the contents of the assembly inside
ILDASM.
![]() What's all this?? COM programming is all about using
interfaces and when you write a COM project in Visual Basic, an interface is
silently created for you behind the scenes with the same name of the class
prefixed with an underscore (_) character. Since we created a class called
CHelloWorld, an interface for the same is created called as _CHelloWorld
which you see above. You can also see that the CHelloWorld class
itlself has been created as an interface that implements _CHelloWorld. Finally,
a concrete class implementation CHelloWorldClass has been
created that implements all the other interfaces. You can now interact with the
COM object using either the CHelloWorld interface or
using the concrete class itself. Here is the code for the .NET
class.
Note that we have programmed the .NET class against the
concrete implementation. Do you see anything new being done?? No. Programming
against an COM object is as similar as programming against another .NET object.
To the developer there is no new thing that needs to be done, except adding the
reference. The rest of the programming paradigm remains the same. Now, when you
execute the project, you will the string being echoed back.
Another method is to use the TLBIMP utility. The Type
Library Importer converts the type definitions found within a COM type library
into equivalent definitions in a common language runtime assembly. The output
of Tlbimp.exe is a binary file (an assembly) that contains runtime metadata for
the types defined within the original type library. For our example DLL created
earlier, here is the TLBIMP command.
The usage of TLBIMP is very simple. You just need to point
it to the DLL and then specify the output file to create. The output then would
be the assembly that you can then reference in your .NET code. In this, you
will add a .NET reference (much like adding references for other .NET
assemblies) and then use it. The usage of the assembly is very similar to the
example shown earlier.
Well, that's all there is to it! In this article we saw
the details of how to interop with COM. Working with COM is extermely simple
(well, for most cases) and .NET pretty much hides all the complexity using the
RCW. Some of the things that you should keep in mind when interoperating with
COM components are:
Have fun!! |
| Home |