Using a .NET Assembly via COM by Jim McKeeth

By: Jim McKeeth

Abstract: Using .NET Assemblies as COM objects in Win32 programs is simple, once you are familiar with the necessary hoops. This article outlines the steps necessary and provides a few tips to improve your experience.

This paper operates on the premise that you are familiar with .NET development and know how to call a COM object in traditional Win32 development. This paper fills in the gap by explaining how you can treat a .NET assembly as a COM object.  Most of the links provided point to articles in the MSDN unless otherwise specified.

Introduction

Using a .NET assembly as a COM object typically requires the following steps:

  1. Giving the assembly a Strong Name.
  2. Placing the assembly in the Global Assembly Cache (GAC).
  3. Registering the assembly as a COM object.
  4. Calling your COM object.

An assembly is not required to be in the GAC if it is in the system or application path, and the strong name is only required if it is in the GAC, but this is the preferred method of using an assembly as a COM object. In the following sections we will take a closer look at each step as well as some other related steps.

All the command line utilities I will mention in this article are part of the .NET Framework SDK, but they may not be in the path when you are at a command prompt. You will either need to explicitly specify the path, copy them to your path, or add the path to your path environment variable (Control Panel / System / Advanced / Environment Variables for 2000 and XP). The usual location for the .NET version 1.1 is C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322 (or WinNT instead of Windows)

Step 1: Strong Names

Strong names provide assemblies with a digital signature and public key to identify it by. This is in addition to the simple name and version number. Because strong names rely on unique, cryptographically secure strong key pairs. No one else can generate the same key pair you generate, and no one else knows your private key, so no one can generate the same strong name. "Assemblies with the same strong name are expected to be identical." Because strong names guarantee uniqueness and equality they allow for side-by-side operation and help to eliminate DLL Hell.

Well that is all fine and dandy, but I am sure you would like to know how one gives their assembly a strong name. The steps to give an assembly a strong name include:

  1. Obtain a key pair.
  2. Assign the key to the assembly.

Lets look at these steps.

Obtain a Key Pair

Your organization may already have a key pair. If this is the case, and the private key is not available to you as a developer for security reasons then you will need to perform a Delayed Signing. If you do have a key pair then you can skip this step.

To obtain a key pair use the Strong Name Tool (Sn.exe) that is provided with the .NET Framework.

To generate a new key pair type the following:

sn -k MyKey.snk

Where MyKey is the name of the key you are creating. Key files typically have an snk extension, but this is not required. If you don't want to deal with a key file, you can place the key in the strong name Cryptographic Service Provider (CSP) with the following command:

sn -i MyKey.snk MyContainer

Now the key from the file MyKey.snk has been placed in the MyContainer container. When you want to remove a container use the following:

sn -d MyContainer

We will talk more about containers in the section on assigning a key to an assembly (next).

Form more information see the tutorial on strong names.

Assign the Key to the Assembly

Once you have a key that you wish to assign to your assembly you need to decide how to assign it to your assembly. As mentioned earlier, if you don't have access to the private key you will need to resort to Delayed Signing.

There are essentially 4 ways to sign an assembly.

  1. Using the Assembly Linker (AL.exe) tool
  2. Using the AssemblyKeyFile attribute
  3. Using the AssemblyKeyName attribute
  4. Using the AssemblyDelaySign attribute

Using the Assembly Linker (AL.exe) tool

The method suggested in most of the documentation on strong names is to use the Assembly Linker (AL.exe) tool. AL has many uses beyond strong names. It allows the generation of a file with an assembly manifest from one or more module or resource files. For now we will just look at using it to sign an assembly.

To sign an assembly, use the following command:

al /out:MyAssembly.dll MyModule.netmodule /keyfile:MyKey.snk

This signs the assembly MyAssembly.dll with the code module MyModule.netmodule using the key found in MyKey.snk. If instead you are using a key stored in a container you would type:

al /out:MyAssembly.dll MyModule.netmodule /keyname:MyContainer

Which uses the key found in MyContainer. For those of you who hate typing you can shorten keyname and keyfile to keyn and keyf respectively, saving 3 key strokes each.

One thing to note about using the AL tool is that rebuilding the assembly will require a re-signing of the assembly.

Using the AssemblyKeyFile attribute

If you are actually able to edit the source code of the assembly then the AssemblyKeyFile attribute is probably a preferred method over using AL since the assembly will be resigned every time you rebuild it.

The syntax for AssemblyKeyFile is simply

[assembly:AssemblyKeyFile( "MyKey.snk" )] // C#

You need to be aware of how the relative path is calculated since that varies based on Language. Both Borland Developer Studio (Delphi or C#) and Microsoft Visual C# base the path on the binary location, while Microsoft VB.NET bases the path on the source code location. Also in C# you will either need to proceed the string with the @ sign, or use double backslashes if specifying a relative path.

One word of caution: When using AssemblyKeyFileAttribute the path and file name persist into the distributed assembly, so you will need to be sure that it does not contain any sensitive information.

Using the AssemblyKeyName attribute

If, instead of a key file, you stored your key within a container in the CSP then you want to use the AssemblyKeyName attribute. With this attribute, instead of specifying the name of the key file you use the name of the container.

[assembly:AssemblyKeyFile( 'MyContainer')] // Delphi

This has the advantage of not needing to worry about relative paths. The disadvantage is that it requires the key be in the container, so could make it harder to share code between machines.

Notice: You should specify a value for AssemblyKeyFile OR AssemblyKeyName. If you specify a value for both then you will receive an error at compilation.

Using the AssemblyDelaySign attribute

As mentioned earlier, if you do not have access to the private key then you need to use the AssemblyDelaySign attribute. This attribute reserves space in your assembly for later signing. The syntax is simply:

[assembly: AssemblyDelaySign( true )]

For more information please see the following:

Step 2: Global Assembly Cache (GAC)

The Global Assembly Cache (GAC) provides a machine wide code depository. This is present on all machines with the Common Language Runtime (CLR) installed. Whenever there is an assembly that is intended to be shared by several applications on the computer then they should be placed in the GAC. As mentioned earlier, the less desirable alternative is to simply place the assembly in the system or application path. There are three ways to place an assembly in the GAC:

  1. Drag and drop the assembly into the GAC
  2. Use an installer that supports placing assemblies in the GAC (preferred for deployment)
  3. Use Global Assembly Cache tool (Gacutil.exe)

The first two options are for systems without the SDK since Gacutil is only available with the SDK. When installing on a system without the SDK you will either need to manually drag it in to the Assembly folder (e.g. C:WinntAssembly) or use an installer that supports the GAC. For now we are going to look at using Gacutil.exe.

Global Assembly Cache tool (Gacutil.exe)

The Global Assembly Cache tool (Gacutil.exe) allows viewing and manipulation of the assemblies in the GAC. For now we are going to look at installing and removing assemblies from the cache. Before an assembly can be installed into the GAC it must have a strong name as covered in step 1.

The command to install an assembly in the GAC is:

gacutil -i MyAssembly.dll

This installs the assembly MyAssembly.dll into the GAC. If you change the assembly you will need to reinstall the new version. Before reinstalling you may need to remove the old one. To remove an assembly, use the following command:

gacutil -u MyAssembly

Notice that instead of using the file name MyAssembly.dll you use the assembly name MyAssembly. Using the file name results in an error.

For more information see the following:

Step 3: Registering

Prior to .NET we registered out COM objects with RegSvr32. In order to register a .NET assembly we need to use the new Assembly Registration Tool (Regasm.exe). The usage of RegAsm is just as simple as RegSvr32 was. To register use the following command:

regasm MyAssembly.dll

If you make a change to the interface you will need to re-register your assembly. If the change is significant enough you will want to unregister the old one first. Generally I have a batch file to unregister with each change. The command to unregister is:

regasm -u MyAssembly.dll

Automating

As mentioned earlier, I use a batch file to automate the installation into the GAC and registering. Here is a sample batch file that assumes the assembly already has a strong name::

@echo off
echo Installing %1. . .
gacutil /nologo /i %1.dll
regasm /nologo %1.dll
echo.
echo Press any key to uninstall. . .
pause >nul
echo.
regasm /nologo /u /nologo %1.dll
gacutil /nologo /u %1
echo.
echo %1 uninstalled.

Then I create a batch file that calls this batch file with the name of the assembly I am working with. Notice that this batch file wants the assembly name, not the file name, and assumes that the file name is the assembly name with a .dll extension. If this is not the case for your project then you will need to adapt this batch file as necessary.

Step 4: Calling your COM object.

Now that you have gone to all the work of registering your assembly as a COM object you need to know how to call it. You call it just like you call any other COM object, but you might be curious what it's Dispatch Identifier (DispID) and the Programmatic Identifier (ProgID). You also might be curious how to prevent some classes and members from not being visible via COM.

ProgID

The ProgID is simply the namespace dot class name. Notice that the Namespace may be named different then the Assembly. So if your namespace is MyNamespace and your class name is MyClass then your ProgID would be:

MyNamespace.MyClass

DispID

A Dispatch ID is a unique integer assigned to a method of a COM object. Depending on how you are making the calls you might alternatively use the method name. You can specify the Dispatch ID in your assembly's source code with the DispId Attribute. If you do not specify one then the Common Language Runtime (CLR) automatically assigns a DispID. For example to set the Dispatch ID to 1 for a method you would use the following:

[DispId(1)]

For more information see:

Visibility

By default all public members are visible to COM when an assembly is registered. If you wish to hide a public member then you can use the ComVisible Attribute. The attribute takes a Boolean as a parameter, and it defaults to true. This seems kind of odd since a member defaults to visible without the attribute. So if you want to hide a member you would use the following:

[ComVisible(false)]

Additional Reading

Another good source of information on this topic, from a slightly different point of view, is Brian Long's .NET Interoperability: COM Interop paper from BorCon 2004. You can also pick up Adam Nathan's book .NET and COM: The Complete Interoperability Guide which is also available in PDF format ebook.

About the Author

Jim McKeeth is president of the Boise Software Developers Group and a Senior Software Engineer for Washington Group International. He co-contributed a chapter on consuming .NET XML web services on Linux using Kylix for the book "Building Web Applications with ADO.NET and XML Web Services". He is named inventor on seven patents, trains and mentors other developers, enjoys writing when he gets the chance and presented at BorCon2003. When not exploring software development he enjoys spending time with his family.

For other papers, components and programs by Jim McKeeth please visit DaVinciUnltd.com. Thanks!

Creative Commons LicenseCopyright ) 2004 by Jim McKeeth - Some Rights Reserved. This work is available for license under an Attribution-NonCommercial-ShareAlike license from Creative Commons Licenses.



Server Response from: ETNASC01