Calling COM Components from .NET Clients

 

Mike Gunderloy
Lark Group, Inc.

November 2001

Summary: In this document, you'll learn the details of calling COM servers from .NET clients. (14 printed pages)

Objectives

  • Understand the concept of Runtime-Callable Wrappers
  • Use TLBIMP to create an Assembly from a COM component
  • Reference a COM component directly from Microsoft® Visual Basic® .NET code

Assumptions

The following should be true for you to get the most out of this document:

  • You are familiar with Visual Basic programming
  • You are familiar with COM concepts. In particular, you should understand the benefits and architecture of componentized software
  • You have access to Visual Basic .NET
  • You understand the overall Microsoft .NET platform architecture

Contents

The Joy of Interoperability
Practice Calling COM from .NET
Using the COM Component Directly
Summary

The Joy of Interoperability

Sometimes a revolution in programming forces you to abandon all that's come before. To take an extreme example, suppose you have been writing Visual Basic applications for years now. If you're like many developers, you will have built up a substantial inventory of code in that time. And if you've been following the recommendations from various language gurus, that code is componentized. That is, by using COM (Component Object Model)—formerly Microsoft ActiveX®—servers, you've broken your application into chunks of callable functionality. Of course, you're also likely to have a substantial investment in components, such as ActiveX controls, from other developers and other companies.

But what if you decide on the radical move of switching development to another operating system entirely? At that point, your entire investment in COM becomes worthless. You can't use any of your existing code, and you have to learn how to do everything on the new platform. Undoubtedly this would be a severe blow to your productivity.

Fortunately, switching from COM to Microsoft .NET involves no such radical loss of productivity. There are two key concepts that make it much easier to move from COM development to .NET development without any loss of code base or productivity:

  • .NET components can call COM components.
  • COM components can call .NET components.

This two-way interoperability is the key to moving from COM to .NET. As you learn the intricacies of .NET, you can continue to use COM components. There are a number of situations where this interoperability is useful:

  • You won't know everything about .NET instantly. It takes time to learn the .NET programming concepts and implementation, so you will probably need to continue critical development in COM.
  • Although Microsoft Visual Basic 6 code can be ported to .NET, the conversion isn't perfect. You may have components that can't be moved to .NET because of implementation or language quirks.
  • You can't write all the code for a large application immediately in a new system such as .NET. It makes much more sense to write components individually and test them one at a time, while still interoperating with your existing code.
  • You may be using third-party COM components for which you do not have source code.

In this document, you'll learn the details of calling COM servers from .NET clients. In a companion article, Calling a .NET Component from a COM Component, you'll learn about calling in the other direction, from COM clients to .NET servers.

Unmanaged Code and Runtime-Callable Wrappers

Code that operates within the Microsoft .NET Common Language Runtime (CLR) is called managed code. This code has access to all the services that the CLR brings to the table, such as cross-language integration, security and versioning support, and garbage collection. Code that does not operate within the CLR is called unmanaged code. All of your legacy COM components are, by definition, unmanaged code. Because COM was designed before the CLR existed, and COM code does not operate within the framework provided by the CLR, it can't use any of the CLR services.

The problem with mixing managed and unmanaged code in a single application is that the unmanaged code isn't recognized in the CLR environment. Managed code components not only depend on the CLR, they expect other components with which they interact to depend on the CLR.

The way out of this dilemma is to use a proxy. In general terms, a proxy is a piece of software that accepts commands from a component, modifies them, and forwards them to another component. The particular type of proxy used in calling unmanaged code from managed code is known as a Runtime-Callable Wrapper, or RCW. Figure 1 shows schematically how RCWs straddle the boundary between managed and unmanaged code. This figure includes a .NET program named NetUI.exe, two COM components named BackEnd.dll and Service.dll, and the necessary technology that connects them.

Figure 1. Calling unmanaged code with RCWs

Converting Metadata with TLBIMP

Of course, .NET is not the first to use metadata to describe public interfaces. In fact, COM type libraries also contain metadata describing the public interface to COM components. The job of an RCW is to convert this COM metadata to .NET metadata.

One tool for performing this conversion is called tlbimp (type library importer), and it is provided as part of the .NET Framework Software Developer Kit (SDK). Tlbimp reads the metadata from a COM type library and creates a matching CLR assembly for calling the COM component.

Note   If you build an ActiveX DLL or ActiveX EXE with Visual Basic, the type library is embedded within the DLL or EXE file.

Tlbmp is a command-line tool with a number of options for such things as signing the resulting assembly with a cryptographic key or resolving external references in the type library. (Type tlbimp /? at a command prompt to see all the options.) The most important option is /out, which lets you specify a name for the resulting .NET assembly. For example, to convert a Visual Basic ActiveX DLL named support.dll to a matching .NET assembly with the name NETsupport.dll, you could use this command line:

tlbimp support.dll /out:NETsupport.dll

Using COM Components Directly

As a Visual Basic .NET developer, you also have the option of using COM components directly. At least, that's what it looks like, although you're programmatically still using a RCW to get to objects in unmanaged code. If you're working within a Visual Basic .NET project, you can follow these steps to add a reference to a COM component:

  1. Click Project, and then click Add Reference.
  2. In the Add Reference dialog box, click the COM tab.
  3. Select the type library you wish to use from the list and click Select, or use the Browse button to locate a component that's not listed. The selected components will be added to the lower listview in the dialog box.
  4. Click OK to create RCWs for the selected type libraries in your Visual Basic .NET project.

When you do this, you'll find that Visual Basic .NET actually creates a DLL in your project's /Bin folder, with a name derived from the original COM component name. For example, if you reference BackEnd.dll version 2.0 in this manner, Visual Basic .NET will create the RCW in the file Interop.BackEnd_2_0.dll.

The major drawback to this shortcut method of using a COM component directly is that there's no opportunity to sign the resulting code. As a result, you cannot place the code in the Global Assembly Cache (GAC), which in turn makes it impossible to share the component among multiple .NET applications.

Choosing Between TLBIMP and Direct Reference

Although either method of using a COM component allows .NET code to call your COM component, there are some reasons to choose one over the other:

  • For a COM component that will only be used in a single Visual Basic .NET project, and which you wrote yourself, use direct reference rather than doing any extra work. This method is only suitable for a truly private component that does not need to be shared.
  • If a COM component is shared among multiple projects, use tlbimp so that you can sign the resulting assembly and place it in the Global Assembly Cache. Shared code must be signed. This method also allows you to create the RCW with a specific name in a specific location.
  • If you need to control advanced details of the created Assembly, such as its Namespace or version number, you must use tlbimp. The direct reference method gives you no control over these details.
  • If you did not write the COM component, neither one of these methods is acceptable. That's because you are not allowed to sign code written by another developer. What you need to do in that case is obtain a Primary Interop Assembly (PIA) from the original developer of the component. MSDN includes links to help you find PIAs for common components such as ActiveX controls.

Practice Calling COM from .NET

In the following example, you will use properties, methods, and events in a COM component from .NET code. You will use tlbimp to convert the COM component's interface to an assembly, and then see how you can treat it like any other .NET component from within managed code. After you've done this, you'll also see how to take a shortcut by using the COM component directly from a Visual Basic .NET project without using tlbimp.

Install the COM Component

The COM component you'll be using in this exercise is named PhysServer.dll. This is an ActiveX DLL that contains a single class named Temperature. This class stores an internal variable representing a temperature and handles conversions between Celsius and Fahrenheit. Table 1 shows the members of the Temperature interface.

Table 1. Interface to Temperature class in PhysServer.dll

Member Type Explanation
Celsius Property Current temperature in degrees Celsius
Fahrenheit Property Current temperature in degrees Fahrenheit
GetCelsius Method Get the current temperature in degrees Celsius
GetFahrenheit Method Get the current temperature in degrees Fahrenheit
BelowFreezing Event Fires when a temperature below freezing is set
AboveBoiling Event Fires when a temperature above boiling is set

To create this COM component on a computer with Visual Basic 6.0 installed:

  1. Launch Visual Basic 6.0 and create a new ActiveX DLL project.

  2. Click on the Class1 module in the Project Explorer window, and use the Properties window to change the name of the class to Temperature.

  3. Click on the Project1 project in the Project Explorer window, and use the Properties window to change the name of the project to PhysServer.

  4. Add this code to the Temperature class:

     Option Explicit
    
    Private mdblCelsius As Double
    Private mdblFahrenheit As Double
    
    Public Event BelowFreezing()
    Public Event AboveBoiling()
    
    Public Property Get Celsius() As Double
        Celsius = mdblCelsius
    End Property
    
    Public Property Let Celsius(NewTemperature As Double)
        mdblCelsius = NewTemperature
        mdblFahrenheit = ((NewTemperature * 9) / 5) + 32
        If mdblCelsius < 0 Then
            RaiseEvent BelowFreezing
        End If
        If mdblCelsius > 100 Then
            RaiseEvent AboveBoiling
        End If
    End Property
    
    Public Property Get Fahrenheit() As Double
        Fahrenheit = mdblFahrenheit
    End Property
    
    Public Property Let Fahrenheit(NewTemperature As Double)
        mdblFahrenheit = NewTemperature
        mdblCelsius = ((NewTemperature - 32) * 5) / 9
        If mdblFahrenheit < 32 Then
            RaiseEvent BelowFreezing
        End If
        If mdblFahrenheit > 212 Then
            RaiseEvent AboveBoiling
        End If
    End Property
    
    Public Function GetCelsius() As Double
        GetCelsius = mdblCelsius
    End Function
    
    Public Function GetFahrenheit() As Double
        GetFahrenheit = mdblFahrenheit
    End Function
    
    Private Sub Class_Initialize()
        mdblCelsius = 0
        mdblFahrenheit = 32
    End Sub
    
  5. Click Project, and from the Visual Basic menu, click PhysServer Properties. In the Project Properties dialog box, change the Project Description to Physical Constants Server. Click OK.

  6. Save the project.

  7. Click File, and to create the COM component, click Make PhysServer.dll.

To install this COM component on your Visual Studio .NET computer:

  1. Create a new folder named Legacy on your hard drive.

  2. Copy PhysServer.dll to the Legacy folder.

  3. Open a command prompt window and type:

    regsvr32 c:\Legacy\PhysServer.dll
    

    You should see the message shown in Figure 2.

    Figure 2. Successful installation of legacy COM component message

Use TLBIMP to Create an Assembly

Next, use the tlbimp utility to create an assembly that provides an RCW for the PhysServer component. From the Control Panel, launch the System applet.

  1. To open a Visual Studio .NET command prompt window, click Start, click Programs, click Microsoft Visual Studio .NET 7.0, click Visual Studio .NET Tools, and then click Visual Studio .NET Command Prompt.

  2. Change to the \Legacy directory.

  3. At the command prompt, type:

    tlbimp PhysServer.dll /out:NETPhysServer.dll
    

In this particular case we've chosen to create the RCW without signing it. If this component were shared between multiple clients, we would need to sign it when we invoked tlbimp. The details of this step are omitted here for brevity. You can learn more about code-signing and the Global Assembly Cache in Calling a .NET Component from a COM Component.

Tlbimp will do the work of conversion and then print a message confirming that the type library has been imported as the new name.

Note   The Visual Studio .NET Command Prompt adds the Framework SDK's /Bin folder to the path so that SDK tools, such as tlbimp, can be invoked without specifying their path.

Create the Test Project

Now you can test the ability of a Visual Basic .NET application to use an object from a legacy COM component.

  1. Open Visual Studio .NET and, from the Start page, choose New Project.

  2. Select Visual Basic Project from the tree view on the left side of the screen.

  3. Select Windows Application as the project template.

  4. Set the name of the application to PhysServerTest and click OK to create the project.

  5. Highlight the form called Form1.vb in the Solution Explorer window and rename it to frmTemperature.vb.

  6. Create the form shown in Figure 3 by adding the appropriate controls and setting the properties of those controls, as outlined in Table 2.

    Table 2. Controls for frmTemperature.vb

    Control Type Property Value
    Label Name lblFahrenheit
      Text Fahrenheit
    TextBox Name txtFahrenheit
      Text (blank)
    Button Name cmdConvertToC
      Text Convert to C
    Label Name lblCelsius
      Text Celsius
    TextBox Name txtCelsius
      Text (blank)
    Button Name cmdConvertToF
      Text Convert to F
    Label Name lblAboveBoiling
      Text Above Boiling!
      Font Size 12
      Font Bold True
      Visible False
    Label Name lblBelowFreezing
      Text Below Freezing!
      Font Size 12
      Font Bold True
      Visible False

    Figure 3. The test form design

  7. To use the RCW for the PhysServer COM component, click Project, and then click Add Reference. Make sure that the .NET tab is selected, and click Browse.

  8. Browse to the Legacy folder and select the NETPhysServer.dll component. Click Open. This will add the component to the Selected Components list, as shown in Figure 4.

  9. Click OK. The References node in the Solution Explorer will expand to show the NetPhysServer reference (along with the default references that the Visual Basic project contains).

    Figure 4. Adding a reference to the RCW component

Add Code to Use the COM Component

Now you're ready to write code that uses the methods, properties, and events of the Temperature class. Click View, click Code, and enter this code after the Windows Form Designer-generated code:

    Private WithEvents moTemp As NETPhysServer.Temperature

    Private Sub cmdConvertToC_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdConvertToC.Click
        lblBelowFreezing.Visible = False
        lblAboveBoiling.Visible = False
        With moTemp
            .Fahrenheit = txtFahrenheit.Text
            txtCelsius.Text = .GetCelsius
        End With
    End Sub

    Private Sub cmdConvertToF_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdConvertToF.Click
        lblBelowFreezing.Visible = False
        lblAboveBoiling.Visible = False
        With moTemp
            .Celsius = txtCelsius.Text
            txtFahrenheit.Text = .GetFahrenheit
        End With
    End Sub

    Private Sub frmTemperature_Load(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles MyBase.Load
        moTemp = New NETPhysServer.Temperature()
    End Sub

    Private Sub moTemp_AboveBoiling() Handles moTemp.AboveBoiling
        lblAboveBoiling.Visible = True
    End Sub

    Private Sub moTemp_BelowFreezing() Handles moTemp.BelowFreezing
        lblBelowFreezing.Visible = True
    End Sub

Note that, as far as this code is concerned, the NETPhysServer.Temperature class is like any other .NET class. You declare an instance of the class and use its methods and properties just as if it were a native class rather than a Runtime-Callable Wrapper around a COM component.

Try It Out

To see the Temperature class in action, follow these steps:

  1. Press F5 to start the project.
  2. Enter 41 in the Fahrenheit text box and click Convert to C. The Celsius box should fill in with the value 5.
  3. Enter 123 in the Celsius box and click Convert to F. The Fahrenheit box should fill in with the value 253.4 and the Above Boiling! label should become visible.
  4. Close the form to stop the project.

Using the COM Component Directly

Now that you've seen how to use tlbimp, try the alternative process of using the COM component directly.

  1. Open a second instance of Visual Studio .NET and from the Start page, choose New Project.
  2. Select Visual Basic Project from the tree view on the left side of the screen.
  3. Select Windows Application as the project template.
  4. Set the name of the application to PhysServerTest2 and click OK to create the project.
  5. Highlight the form called Form1.vb in the Solution Explorer window and rename it to frmTemperature.vb.
  6. Switch to the original instance of Visual Studio .NET and open frmTemperature in design view. Click Ctrl-A, Ctrl-C to select all of the controls on the form and copy them.
  7. Switch back to the second instance of Visual Studio .NET, select frmTemperature in design view, and click Ctrl-V to paste all of the controls onto the new form.

To use the COM component directly, click Project, click Add Reference and select the COM tab in the Add Reference dialog box. Scroll down the list of COM components until you find Physical Constants Server, click Select, and then click OK. You'll receive the warning shown in Figure 5.

Figure 5. Warning that appears when inserting a COM component reference

At this point, the proper response depends on who built the COM component. If it came from a vendor or another developer, you should click No and then obtain a Primary Interop Assembly from that vendor or developer. In this case, the component is private to your own development, so click Yes to tell Visual Studio .NET to generate an RCW for this component. You'll see PhysServer appear in the list of References in the Solution Explorer.

Note   This is the original PhysServer.dll, not the RCW that you generated earlier by using tlbimp.

Add Code to Use the COM Component

Now you're ready to write code that uses the methods, properties, and events of the Temperature class. Click View, click Code, and enter this code after the Windows Form Designer-generated code:

    Private WithEvents moTemp As PhysServer.Temperature

    Private Sub cmdConvertToC_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdConvertToC.Click
        lblBelowFreezing.Visible = False
        lblAboveBoiling.Visible = False
        With moTemp
            .Fahrenheit = txtFahrenheit.Text
            txtCelsius.Text = .GetCelsius
        End With
    End Sub

    Private Sub cmdConvertToF_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdConvertToF.Click
        lblBelowFreezing.Visible = False
        lblAboveBoiling.Visible = False
        With moTemp
            .Celsius = txtCelsius.Text
            txtFahrenheit.Text = .GetFahrenheit
        End With
    End Sub

    Private Sub frmTemperature_Load(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles MyBase.Load
        moTemp = New PhysServer.Temperature()
    End Sub

    Private Sub moTemp_AboveBoiling() Handles moTemp.AboveBoiling
        lblAboveBoiling.Visible = True
    End Sub

    Private Sub moTemp_BelowFreezing() Handles moTemp.BelowFreezing
        lblBelowFreezing.Visible = True
    End Sub

Note   With the exception of the library name, this is exactly the same code that you used in the first version of the PhysServerTest project.

Try It Out

To see the Temperature class in action, follow these steps:

  1. Press F5 to start the project.
  2. Enter 77 in the Fahrenheit text box and click Convert to C. The Celsius box should fill in with the value 25.
  3. Enter -17 in the Celsius box and click Convert to F. The Fahrenheit box should fill in with the value 1.4 and the Below Freezing! label should become visible.
  4. Close the form to stop the project.

Summary

Although calling a COM component from managed code is simple, you should not consider this a permanent solution for most applications. Over the long run, you'll want to migrate heavily used components to native .NET code. This will provide you with the full range of .NET capabilities, as well as make your code faster by eliminating the RCW layer.

When you're starting to move your development from Visual Basic to Visual Basic .NET, though, the ability to call COM components from .NET code is essential. As you've seen, it's also easy to implement. Treat this technique as an important part of your migration strategy.

About the Author

Mike Gunderloy writes about software and raises chickens in eastern Washington State. He is co-author of Access 2002 Developer's Handbook and author of SQL Server Developer's Guide to OLAP with Analysis Services, both from Sybex. He's been writing code for Microsoft products since the prehistoric pre-Windows era, and has no intention of stopping any time soon.

About Informant Communications Group

Informant Communications Group, Inc. (www.informant.com) is a diversified media company focused on the information technology sector. Specializing in software development publications, conferences, catalog publishing and Web sites, ICG was founded in 1990. With offices in the United States and the United Kingdom, ICG has served as a respected media and marketing content integrator, satisfying the burgeoning appetite of IT professionals for quality technical information.

Copyright © 2001 Informant Communications Group and Microsoft Corporation

Technical editing: PDSA, Inc., or KNG Consulting