HomeDigital EditionSearch Dotnet Cd
ASP.NET C# Certification Exams The CLI Data Access Editorials Extending .NET Fundamentals Interoperability Interviews Migrate Mobile .NET Mono .NET Interface Object-Oriented Programming Open Source Optimization Product/Book Reviews Security Source Code UML Visual Studio .NET

Thanks to Microsoft's foresight you don't have to rewrite all of your COM functionality in .NET in order to take advantage of .NET's capabilities. In this article, the second of a two-part series, I will show you how to consume .NET classes from COM. This article assumes you are familiar with COM type libraries and method calling, at least at the conceptual level, and know the basics of C# programming.

Consuming .NET Classes from COM
How hard can using .NET classes in COM be? Not too hard, actually. The first thing you might ask is when or why you would want to expose a .NET component to a COM client. After all, if you are writing new code, wouldn't it just be easier to write the client code in .NET as well? Often, the answer will be no. If you have a large-scale legacy application that you need to maintain and continue to evolve, you may not have the luxury of rewriting the whole thing, or even significant portions of it from scratch in .NET. But if the customer is asking for new features, it doesn't make sense to code those new features using legacy technologies and languages either.

By coding the new capabilities as .NET components, you can plug those capabilities into your legacy code base with only minor coding requirements on the legacy side. You have new code that not only can be easily consumed by the legacy application as a COM component, but the same code can be reused and extended by new .NET applications that you build now and in the future. Not only does this make your code more maintainable and extensible, you can also capitalize on the vast capabilities in the .NET Framework class libraries and third-party frameworks and components being built for .NET, and create your new capabilities a lot faster and with a lot less code.

The first step in using a .NET object from a COM client is to declare the class you want to expose. Almost any class in .NET can be exposed to a COM client simply by exporting the information for its assembly and registering the assembly with COM. The class must have a default constructor, and the methods, properties, fields, and events you want to expose must be public. To expose a class in a fashion that adheres to the rules of COM requires a little more planning.

Remember that a COM component's interface must never change once published. The component coclass and the interfaces are identified with GUIDs (globally unique identifiers) that should not be changed either. You also need to think about what other architectural constraints there may be, such as what the threading model should be, whether interfaces need to be custom, dispatch, or dispatch only. Most of the architectural aspects you need to specify can be set using .NET attributes. Table 1 shows a list of the most common COM-related attributes and what they do for you. Most of these are covered in more detail shortly.

Table 1

Each .NET class that you expose to COM will have a default class interface associated with it. The name of the interface is the name of the class prefixed by an underscore, and by default it is a dispinterface (dispatch only). You can make this explicit using the ClassInterface attribute on your class as shown here.

[ ClassInterface( ClassInterfaceType.AutoDispatch ) ]
class MyClass {
...
}

You can also use the AutoDual type if you want an IDispatch dual interface for the class interface, but this is not recommended for reasons I'll discuss shortly. You cannot make the class interface IUnknown-based, but you can make other interfaces either dispinterfaces, dual, or custom using the InterfaceType attribute.

In .NET 1.0, the default ClassInterfaceType is AutoDispatch. When the .NET type information is exported to a type library, the type information from AutoDispatch interfaces is not placed in the type library. Since they can only be called dynamically, this leaves it up to the runtime to locate the method by name and keeps clients from caching the DISPID of the method, which the runtime is free to change if it chooses.

Exposing your class functionality to COM clients on the class interface is a really bad idea though, because it is almost guaranteed to break the cardinal rule of COM ­ an interface should never be changed after it is published. The class interface is guaranteed to change every time you add a new public member to the class or one of its parents. So exposing your functionality on the class interface is not a very extensible solution since it means you can no longer change your class after publishing it.

The right way to do things is to define public .NET interfaces that contain the methods, properties, and events that you want to expose using good component-based design principles. These principles include trying to keep the number of methods on one interface to a reasonably small set, and to make sure that all the methods on an interface are functionally related to the intended purpose of the interface. Once you have defined the interfaces, their methods, properties, and events, then you should implement these interfaces in the .NET class you want to act as your COM coclass. The interfaces must be marked as public, and will default to being IDispatch derived in COM. You can use the InterfaceType attribute to change this.

When you define your classes in this fashion, they are not only more usable from COM, they can be used in a good component-based fashion from your other .NET classes as well. It also models the way components were built in VC6 and VB6.

Besides not relying on the class interface to expose your functionality, another good approach is to block it from use and set one of the other interfaces as the default interface. You do this by setting the ClassInterfaceType for the class to None. Then the first interface that your class derives from (the first one after the colon in the class declaration) will become the default interface.

So to make all this concrete, I'll show you how to implement a simple component for a COM client using .NET and C#. Let's say you have a VB6 application to which you want to add the capability to convert an image file from a JPEG to a GIF or vice versa. Doing this in VB6 or VC6 would require a third-party library or a lot of messy, low-level C++ code. Doing this with the .NET Framework is a piece of cake, so I will add this functionality using a .NET class exposed to the VB6 app as a COM component. In this example, for simplicity I just have the class save the converted image to another image file. While you could marshal the converted bits back to an unmanaged application through a Variant or other means in COM, the code to do so gets a little tricky and is beyond the scope of this article.

The first step is to define the interface.

[Guid("920EDD21-B27D-4137-A40A-C9BC605C494C")]
public interface IConvertImages
{
void ConvertJpgToGif(string jpgFilePath,
string gifFilePath);
void ConvertGifToJpg(string gifFilePath,
string jpgFilePath);
}

Note that we use the Guid attribute to deterministically assign the associated IID for this interface. This is an important step, because otherwise the export tools will automatically assign one, and it will change each time you modify the interface, compile, and export, which can clutter up your registry horribly.

The class definition to implement this conversion functionality is shown in Listing 1.

I use the ClassInterfaceType.None to block the generation of a default class interface, and I assign a Guid for the coclass CLSID for the same reasons mentioned for the IID above. If you are programming using VB.NET, there is also a ComClass attribute to specify the CLSID, IID, and event interface IID. As you can see, thanks to the Bitmap class in the .NET Framework, it only takes a couple of lines of code to provide this functionality. Place all this in a file named ImageConverter.cs, and add a namespace declaration around it all with the namespace SimpleImageProcessor:

namespace SimpleImageProcessor
{
interface IConvertImages . . .
class ImageConverter . . .
}

To get this to compile, you will need to add the following three namespace references to the top of the source file:

using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

In order for COM to be able to get to the assembly at runtime and use it as a COM object, the assembly has to be registered with COM, and it has to be either placed in the same folder as the host executable, or registered in the .NET global assembly cache (GAC) so that the runtime can find it. In order to add the assembly to the GAC, you will need to digitally sign it. To digitally sign it, you need a strong name key file, and will need to compile that key file into your assembly.

So let's step through what it takes to accomplish all of that. First, you create the strong name key file using the Strong Name Tool (sn.exe). You run sn.exe as follows to create a key file:

sn ­k demoKey.snk

You could use sn.exe and the Assembly Linker tool (al.exe) to add the key file to your assembly after you have compiled it, but a much easier way is to use the AssemblyKeyFile attribute in your code to compile it in at compile time. You can add this attribute to any of the source files that get compiled into the assembly, as long as it is only declared once, and is at assembly scope (i.e., outside the namespace declaration for your code). For our example, add the following line to the top of the ImageConverter.cs file, immediately beneath all the "using" namespace statements and above the SimpleImageProcessor namespace declaration.

[assembly: System.Reflection.AssemblyKeyFile("demoKey.snk")]

Now we need to compile the code into a .NET assembly. From the command line, use the following command:

csc /t:library ImageConverter.cs

The /t (or /type) switch tells the C# compiler to make the resulting assembly a class library (or DLL) instead of using the default packaging of an EXE.

The next step is to create the type library and register the assembly. You can create the type library with the tlbexp.exe tool, then call regasm.exe to do the registration, or you can do it all in one fell swoop using the /tlb switch on regasm:

regasm /tlb:ImageConverter.tlb ImageConverter.dll

You could now fire up the OLE/COM Viewer and take a look at the type library information if you want to see that it is all in there, just like with any other COM component. For this example, the type library will end up named ImageConverter, the same as the DLL. If you open the type library in OLEView, you will see that in fact the interface and coclass are there, but the _ImageConverter class interface is not, since we applied a ClassInterfaceType of None (see Figure 1). You will also see that in fact the IConvertImages interface is marked as the default interface since it was the first interface that the ImageConverter class derived from.

Figure 1

Even though COM can find all the type information it needs to describe the component now, there is one last step before you will be able to actually instantiate the component from a client application. You must either copy the .NET assembly (ImageConverter.dll) into the same folder as the host executable that will be using it, or you must register the assembly in the GAC. I will take the latter approach to show how it is done. Deploying the assembly with the host executable is actually the preferred method because it avoids cluttering up the GAC with unneeded shared assemblies. But if your component might be used by more than one host application, then the GAC is the way to go.

Registering the assembly in the GAC is as easy as running one more command from the command prompt.

gacutil ­i ImageConverter.dll

You now have a full-fledged .NET COM component that can be consumed from any COM client. Download the code from www.sys-con.com/dotnet/sourcec.cfm for a sample VB6 client.

All this command-line stuff is fine and good, but shouldn't it be easier in VS.NET? A little. VS.NET will automatically take care of the regasm step for you if you go into the Project properties, under the Configuration Properties\Build node in the tree, and set the Register for COM Interop property to True. However, you will still need to use the command-line tools to generate the key file and to put the assembly into the GAC.

These steps can be added to your Tools menu as an external tool to keep you from having to fire up a command prompt. Unfortunately, VS.NET 2002 did not include custom build steps for C#, but VS.NET 2003 does include custom build steps with which you could register the assembly in the GAC or perform other command-line options or run scripts.

One way around this is to add a C++ Makefile project to your solution with a project type of Utility, and put the custom build steps in that project as the pre- or post-build commands. Then you can set up the project dependencies and build order between that and your primary project with the .NET solution, and you achieve the same thing. See the download code SimpleImageProcessor solution for an example of this approach.

The download code also contains a VB6 project called ImageConverterClient that shows how easy it is to then use the .NET component in a VB6 client. A VC6 client would be just as simple, no harder than using any other COM component.

Back in the Beta 1 days I wrote a couple articles and an e-book on COM interop. In those, I dedicated an entire section to exposing .NET controls as ActiveX controls to ActiveX control containers. Regrettably, Microsoft removed the capability to do that for good in Beta 2. There is no straightforward way to get a .NET control exposed as an ActiveX control in the release of .NET. There are workarounds and manual methods out there involving implementing all the required ActiveX control interfaces by hand, but they are not really practical for production development. So if you want to develop your GUI controls in .NET, you are going to need a .NET container application to host them. You can also create an unmanged C++ shim control that hosts the .NET runtime and wraps .NET controls to expose them as an ActiveX control, but that technique is quite advanced as well.

Conclusion
I've taken you through a whirlwind tour of creating interoperable code between .NET applications and COM components, and COM clients and .NET components. Using these capabilities, the doors open up considerably to progressively migrate your existing applications to .NET without having to trash everything you have done before and start over. There is a lot more capability in .NET and the runtime to perform far more complex COM interoperability than I have described in this series. If you are planning on migrating an application with complex COM components that pass a variety of data and types as method parameters, it behooves you to dig into the COM interoperability sections of the MSDN documentation and see what all you will want or need to do to get the most possible reuse out of your existing code.

Author Bio
Brian Noyes is an independent software consultant with Software Insight (www.softinsight.com). He is an MCSD with over 11 years of programming, software development, and project management experience. He is a contributing editor to numerous programming publications, author of .NET and COM: Working Together, and contributing author to Sams Teach Yourself DirectX 7 in 24 Hours. brian@softinsight.com

	



Listing 1: 

The ImageConverter Class implements the functionality exposed to COM clients.

[ClassInterface(ClassInterfaceType.None)]
[Guid("3957E444-EA85-4f49-AAD4-7DDAB6A12324")]
public class ImageConverter : IConvertImages
{
   public ImageConverter() {}
   public void ConvertJpgToGif(string jpgFilePath,
                               string gifFilePath)
   {
      Bitmap bmp = new Bitmap(jpgFilePath);
      bmp.Save(gifFilePath, ImageFormat.Gif);
   }
   public void ConvertGifToJpg(string gifFilePath,
                               string jpgFilePath)
   {
      Bitmap bmp = new Bitmap(gifFilePath);
      bmp.Save(jpgFilePath, ImageFormat.Jpeg);
   }
}

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.

  E-mail: info@sys-con.com