COM Interoperability

Mark Strawmyer Presents: .NET Nuts & Bolts


Welcome to the next installment of the .NET Nuts & Bolts column. In this column, we'll explore COM Interoperability, also known as COM Interop. COM Interop is the means to which we can use COM objects in a .NET application and build .NET objects that appear to be COM objects.

Why We Need COM Interop

There are fundamental architecture differences, possibly the understatement of the year, that render COM and .NET objects incompatible. Key differences include, but are not limited to, the following:

  • As COM objects are created and stored in memory, the clients are given a reference to a specific memory location. The objects are not expected to move throughout their lifetime, and COM has no way to deal with object references that change memory locations because the clients have reference to a specific location. In .NET, managed objects are stored in memory controlled by the common language runtime (CLR). These objects may be allocated, de-allocated, or reallocated as deemed appropriate by the CLR. All client references to these objects are updated automatically, based on whatever action the CLR takes.
  • COM objects expose a set of functions used to track the number of references to an instance of the object. Clients are responsible for using these functions to manage the lifetime of object instances. In .NET, the CLR, not the client, manages the lifetime of all objects.

Companies have resources, in the form of time and money, invested in building and/or buying COM components. In many cases, it is not justifiable to simply throw away these investments. You then have no choice but to wait to until the application has outlived its lifetime and can be replaced in order to benefit from .NET. Thus, a need exists to allow organizations to leverage current COM investments and also benefit from the advantages of .NET applications by easing them into their applications. This is the role COM Interop plays and the benefit it provides.

How COM Interop Works

It stands to reason that the work required to get the .NET and COM components to interoperate is going to be on the .NET-side because it is the newer technology. If you had to change all of your COM components to operate with .NET, it would make sense to just rewrite them in .NET and you wouldn't be reading this article on COM Interop.

To allow communication between .NET and COM objects, the CLR provides wrappers to make each object think it is talking to an object from its own architecture. A .NET client calling a COM object results in the CLR using a runtime callable wrapper (RCW) to make the .NET client think it is talking to a .NET object. A COM client calling a .NET object results in the CLR using a COM callable wrapper (CCW) that makes the COM client think it is talking to another COM object. The wrappers facilitate built-in rules for marshaling data between .NET and COM, such as conversion of string to BSTR and vice versa. Typically, the generated wrappers provide adequate marshaling of types between .NET and COM. There are ways to adjust way these wrappers are generated, but that is beyond this initial look at COM Interop.

Strong Naming

It is important to understand strong naming because it plays a role in COM Interop. A strong name is one that is globally unique to the particular assembly. This helps to avoid common DLL conflicts, a.k.a. DLL hell, such as naming conflicts and versioning issues. They also provide a security check that the contents of the assembly have not changed since it was last compiled. If the .NET object calling a COM component is strong named, the COM component needs to be strong named as well; otherwise, the advantages of strong naming are lost. If it is a COM client calling a .NET object, the .NET object needs to be strong named so that the CLR can resolve the name to the appropriate assembly.

How to Make an Assembly Strong Named

  1. Generate a key file at a command prompt: sn.exe -k key.snk.
  2. Add an attribute to the AssemblyInfo.cs file that references the generated key file: <Assembly: AssemblyKeyFile("..\..\key.snk")>. The path in the attribute is relative to the project output directory. A further explanation can be found in the default AssemblyInfo.cs file itself.

Use an Unmanaged COM Object in a Managed .NET Application

If you are doing new development using .NET and you need to utilize existing COM investments, this is the section for you. In my opinion, this is the most likely case in which you will need to use COM Interop. The process is different if you are planning to use strong naming for your assemblies or not. Both scenarios are outlined below, followed by an example scenario with some supporting code.

Option 1: .NET Client Object is not Strong Named

  1. Create a runtime callable wrapper (RCW) for the COM component so that the CLR can interact with the object as a managed type. There are a number of ways to generate the RCW. A couple of the more common ways are listed below.
    • Use the Type Library Importer utility (tlbimp.exe). It is a manual command line driven utility that accepts different arguments. It converts a COM-specific type definition from a COM type library into equivalent definitions for .NET using a wrapper assembly. Example: tlbimp <TLB name>.tlb.
    • An easier way is to let Visual Studio .NET do the work for you by clicking the Project menu, Add Reference menu item, COM tab, and then double-click the desired COM component from the list of registered components. Click OK to close the dialog, and the wrapper is now generated.
  2. Reference the wrapper assembly DLL in the project. This will have been done for you automatically if you used Visual Studio .NET back in Step 1.

Option 2: .NET Client Object is Strong Named

Because the .NET client is strong named, we must make sure that all of the components utilized are also strong named; otherwise, we lose the benefits of strong naming.

  1. Generate a key file at the command line. Example: sn.exe -k <Key File name>.snk.
  2. Use the Type Library Importer (tlbimp.exe) to generate a strong named wrapper assembly. Example: tlbimp /keyfile:<Key File name>.snk <TLB name>.tlb.
  3. Reference the wrapper assembly DLL in the .NET project.

Sample COM Object

For the sake of this example, we have a COM object that contains a function that will pad the left side of a string with a specified string until it reaches a desired length. We'll pretend this is the greatest version of a pad function ever written and that we are compelled to reuse it in its current form. This function has been compiled in a Visual Basic 6.0 class called clsCommon that is part of an ActiveX dll called SampleUtil.dll when compiled. The code for the COM component is located below, and the client .NET code is located in the trailing section.

'****************************************************************
' Description:  Left pad the given string with the given string
'               until it reaches a string of the desired length.
'
' Parameters:   v_strInput  - string to pad
'               v_strPad    - string to pad with
'               v_intLength - desired string length
'
' Return Val:   String - string left padded with given char
'****************************************************************
Function leftPad(ByVal v_strInput As String, _
                 ByVal v_strPad As String, _
                 ByVal v_intLength As Integer) As String
On Error GoTo ErrorCode
         
Dim intCount  As Integer            ' Loop control
Dim intLenPad As Integer            ' Length of the pad string
Dim strOutput As String             ' Output string
    
    intCount  = Len(v_strInput)
    intLenPad = Len(v_strPad)
    strOutput = v_strInput
    
    While (intCount < v_intLength)
        strOutput = v_strPad & strOutput
        intCount  = intCount + intLenPad
    Wend
    
    leftPad = strOutput
    
ErrorCode:
    If (Err.Number <> 0) Then
        leftPad = v_strInput
    End If
End Function

Sample .NET Application

I did not use a strong name for this example, so the only thing required to reference and use the COM component was to simply go through the Visual Studio .NET menus and add a reference to the SampleUtil COM component. The sample .NET client is below.

/// <remarks>
/// Sample client to use the SampleUtil COM component.
/// </remarks>
public class SampleUtilClient
{
  public SampleUtilClient()
  {
    SampleUtil.clsCommon utility = new SampleUtil.clsCommon();
    string test = utility.leftPad("testing", "0", 50);
  }
}

Use a Managed .NET Object in an Unmanaged COM Application

There are several reasons why you may want to use a .NET object in a COM application. A likely scenario is that you are making changes to an existing COM component and you want to leverage some new functionality that was built using .NET.

If you intend to call .NET objects from COM, there are a few items to remember due to the different capabilities of .NET and COM. A list of some of the items is outlined below.

  • Do not use parameterized constructors.
  • Do not rely on the constructor to have been called prior to other method invocations.
  • Do not use static methods.
  • The .NET object needs to be strong named and resolvable at runtime.
  • Assign GUIDs to all .NET objects so that the appropriate COM required registry entries can be created. The GUIDs are assigned to the .NET object by adding the System.Runtime.InteropServices.Guid attribute to the class definition. The GUID can be generated in Visual Studio .NET by going to the Tools menu and select the Create GUID option. Choose the "Registry Format" option and copy and paste the value into the attribute. Make sure you remove the leading '{' and trailing '}' characters from the GUID.

The process for using a .NET object in a COM client is described below.

  1. Establish a strong name for the .NET object so that the CLR knows exactly what assembly the COM component is referring to.
  2. Create a COM callable wrapper (CCW) for the .NET assembly.
    • In order for the COM client to find the .NET object, you must make the Registry entries required by COM. The RegAsm.exe command line utility will generate the required COM type library and make the appropriate Registry entries. If you want to have your .NET object show up as a Windows 2000 Component Service, you can use the RegSvcs.exe utility instead. The RegSvcs.exe utility can generate and register the COM type library and will also install the object into a COM+ application.
    • Another way to make it available is to set the project options in Visual Studio .NET to allow it to register the .NET object as a COM component. Go to the Project menu and select Properties. Select the Configuration Properties. Underneath the Build options, there is a flag for Register for COM Interop. If you set this to true, Visual Studio will perform the RegAsm.exe step for you automatically.
  3. Reference the wrapper assembly in the COM project.

Sample .NET Object

Let's use the scenario where we have an existing COM application. We can't simply rewrite the whole thing at once, so we are going to transition into development with .NET. We found a great and efficient benefit to do our left pad function with .NET that we had previously done in COM. We'll write it in .NET and then utilize it inside our existing COM application.

Here is the class code for our assembly. I generated the GUID in this example by using the steps above within Visual Studio using System:

namespace COMInteropUtil
{
  /// <remarks>
  /// Utility class to be called by a COM component.
  /// </remarks>
  [System.Runtime.InteropServices.Guid(
"6CF0486C-692B-4591-80D5-0C84D6CD5901")]
  public class SampleUtil
  {
    /// <summary>
    /// Left pad a string until it reaches a string of the
    /// desired length.
    /// </summary>
    /// <param name="source">Source string</param>
    /// <param name="padPhrase">Phrase to pad with</param>
    /// <param name="totalWidth">Total length of padded
    ///  string</param>
    /// <returns></returns>
    public string LeftPad(string source,
                          string padPhrase,
                          int totalWidth)
    {
  int count     = source.Length;
  int lengthPad = padPhrase.Length;
  string output = source;

  while( count < totalWidth )
  {
    output = padPhrase + output;
    count += lengthPad;
  }

  return output;
    }
  }
}

Here are the relevant entries from the AssemblyInfo.cs file:

[assembly: AssemblyTitle("CodeGuru COM Interop Sample")]
[assembly: AssemblyDescription("CodeGuru COM Interop Sample")]
[assembly: AssemblyKeyFile("..\\..\\key.snk")]

Sample COM Application

In order to test this, I simply created a new standard EXE project in Visual Basic 6.0. I added a reference to the newly registered DLL with the description "CodeGuru COM Interop Sample" as it was dictated in AssemblyInfo.

Private Sub Form_Load()

    Dim strTest As String
    Dim objInterop As New COMInteropUtil.SampleUtil
    strTest = objInterop.LeftPad("test", "pad", 50)
    MsgBox strTest

End Sub

Here is the output from executing the application.

Possible Enhancements

In past columns, we've built some starter objects that you can grow and develop to fit your needs. In this column, we did not build any such objects. As a result, the possible enhancements this time around involve additional COM Interop concepts you can consider exploring.

  • COM and .NET both have their own way of handling errors. COM returns special values from functions to indicate an error condition. .NET handles errors through by throwing and catching exceptions. The wrappers (CCW and RCW) will handle converting the errors to the appropriate style. It is conceivable that this default mapping will not occur as desired and therefore it may be necessary to use a custom wrapper or customize this process in some way.
  • Combining the use of managed and unmanaged threading.
  • Custom marshaling—control how a .NET object is exposed to COM or a COM object is exposed to .NET.

Future Columns

The next column is yet to be determined. It is likely I am going to dive into some of the items in the EnterpriseServices namespace. If you have something in particular that you would like to see explained here, you can reach me at mstrawmyer@crowechizek.com.

About the Author

Mark Strawmyer, MCSD, MCSE (NT4/W2K), MCDBA is a Senior Architect of .NET applications for large and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in architecture, design, and development of Microsoft-based solutions. You can reach Mark at mstrawmyer@crowechizek.com.



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • Getting an overview

    Posted by DudeGuru on 01/10/2006 11:25am

    Easy to understand explanation

    Reply
  • Warning: VS 2005 change to AssemblyKeyFile

    Posted by chrynnon on 11/08/2005 01:59am

    There is a change to the way VS 2005 handles key signing, which will give you a compile error. See http://www.logos.ms/Use%20command%20line%20option%20keyfile%20or%20appropriate%20project%20settings%20instead%20of%20AssemblyKeyFile.htm

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As mobile devices have pushed their way into the enterprise, they have brought cloud apps along with them. This app explosion means account passwords are multiplying, which exposes corporate data and leads to help desk calls from frustrated users. This paper will discover how IT can improve user productivity, gain visibility and control over SaaS and mobile apps, and stop password sprawl. Download this white paper to learn: How you can leverage your existing AD to manage app access. Key capabilities to …

  • Learn how cloud-based master data management (MDM) empowers your fast-paced business to get the right data to the right place in real time, so you can remain competitive and agile.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds