Consuming .NET Components from COM-Aware Clients'�A Simple Tutorial

Thursday Jul 31st 2003 by Steve Green
Share:

A tutorial on consuming .NET components from COM-aware clients.

Environment: .NET

Sometimes, it's useful to create COM components in C# and access them from unmanaged code (such as C++, VB, and JavaScript). The .NET runtime seamlessly allows unmanaged COM-aware clients to access .NET components through the COM interop. This ensures that COM-aware clients can talk to .NET components.

In this article, we are going to have a C++ client talk to a C# component.

Enter the following small example code into your favourite editor:

using System;
using System.Runtime.InteropServices;

public interface IHelloDotNet 
{

  int GetAge();

}    /* end interface IHelloDotNet */

[ClassInterface(ClassInterfaceType.None)]
public class HelloDotNet : IHelloDotNet
{
  public HelloDotNet()
  {
  }

  public int GetAge()
  {
    return 20;
  }


}    /* end class HelloDotNet */

Please note that the above is only one way to write components in C#. We could actually get .NET to auto generate the interface for the class for us, but the method of manually generating the interface and creating a class which derives from this interface is the best method (if not the most tedious) of creating C# components. Because COM interfaces are immutable, having interfaces auto generated would lead to versioning problems; any class changes would break unmanaged COM clients.

The first thing to note in this C# client is the interop attribute ClassInterface. This is one of many interop service attributes. Note that we set the attribute to ClassInterfaceType.None. This means that no class interface is generated for the class; this is correct because we have manually created our own interface. Going back to having this interface automatically created, we would use ClassInterfaceType.AutoDual. This would create a dual interface for the class and also make typeinfo available to the type library.

Now, to compile the component to a DLL, issue the following from a command prompt:

csc /target:library /out:HelloDotNot.dll HelloDotNet.cs

This generates a .NET assembly that, at the moment, COM-aware clients are clueless about. We need to get some COM-friendly type information from it so that our C++ client will be happy to play around with it. What we need to do is take in a .NET assembly and generate a typelibrary out of it so it is usable from a COM-aware client. The .NET framework provides a couple of tools to do this. We are going to use the RegASM tool; this will register the assembly and generate the type library in one go. Type in the following:

regasm HelloDotNet.dll /tlb:HelloDotNet.tlb

Now, let's get down to consuming this .NET component!

I wrote a quick C++ MFC dialog application to do this, so fire up Visual C++ and create a MFC dialog application, throw a button on the dialog, and create a handler for it. Enter the following code behind the button handler:

::CoInitialize(NULL);

HRESULT hr = spHelloNet.CreateInstance(__uuidof(HelloDotNet));
long Age   = spHelloNet->GetAge();

Simple enough, but where do spHelloNet and HelloDotNet come from?!? Easy, the type library we generated for the .NET component. So, go to the top of your dialog code and enter the following:

#import "HelloDotNet.tlb" no_namespace
IHelloDotNetPtr spHelloNet = NULL;

.NET automatically generated the IHelloDotNetPtr for us. Don't worry about it, just type it in and forget about it.

Okay, almost done. What you need to do now is drop the type library file into your client source area and copy the HelloDotNet DLL into the executable area (example: debug or release folder). We have to do this because we have not made the component shareable. By this, I mean being able to be seen by any application; at the moment it is a private component. Now, slam a break point on the line that reads spHelloNet->GetAge() and run the program. When you step over this line, the result should be 20! Violà; we have called a .NET component from an unmanaged C++ application—groovy!

GAC—Making Our Component Globally Available

I'm getting fed up with copying the type library and DLL across. How can I prevent this? There is a way of registering the component for global use. Yet again, Microsoft has thought of everything. We want our .NET assembly to be used in multiple applications; thus, it must be registered in what is called the Global Assembly Cache (GAC). This applies not only to .NET components used from .NET, but also .NET components used from COM.

Before adding assemblies to the GAC, they need to be strongly named. That sounds strange?! A strong name consists of the assembly identity (name, version, and culture), plus a public key and digital signature. This strong name is used for several reasons—it guarantees the following three things:

  • Uniqueness
  • Version protection
  • Code integrity

The first one of these is the important one. It assures us that no two will ever be the same.

Okay, enough dribble. How do I create this strong name? We use the sn utility, an example of which is shown below:

sn -k HelloDotNet.snk

This generates a key pair and stores it in the file named HelloDotNet.snk. At this point, the assembly knows nothing about the strong name. Let us change that by adding the following line to the C# code:

[assembly:AssemblyKeyFile("HelloDotNet.snk")]

You may need to put the correct path in the above also, to where the .snk file is located.

Now, we need to install the assembly into the GAC. We are going to use yet another utility, called gacutil. Enter the following:

gacutil /I HelloDotNet.dll

That is it; the component is now global and can be used with any application!

I hope this article has been of some use. I've tried not to go into any gritty details, just mainly the bare bones. Any comments and criticisms are welcome.

Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved