Calling a .NET Component from a COM Component
Lark Group, Inc.
Summary: Walks through the details of how to call Microsoft .NET servers from COM clients. (12 printed pages)
- Understand the concept of COM callable wrappers
- Create a .NET server that can be called from Microsoft® Visual Basic® 6.0
- Use the sn, regasm, and gacutil utilities
- Write Visual Basic 6.0 code that uses a .NET class
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
- You have access to Visual Basic .NET
- You understand the overall .NET architecture
- You understand how to create public classes in Visual Basic .NET
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. This would undoubtedly be a severe blow to your productivity.
Fortunately, switching from COM to .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:
- The switch to .NET won't be instant. It takes time to learn the .NET programming concepts and implementation, so you will probably find that you need to continue working with COM code while you, your coworkers, and your suppliers come up to speed.
- The code that you can migrate to .NET can't be migrated all at once. You'll want to migrate, and then test, each migrated component individually.
- You may be using third-party COM components that you cannot convert to .NET, and where the supplier has not yet released a .NET version.
- Although Visual Basic 6.0 code will migrate to .NET, the migration isn't perfect. You may have components that can't be moved to .NET because of implementation or language quirks.
In this document, you'll learn the details of calling .NET servers from COM clients. In another document in this series, Calling COM Components from .NET Clients, you'll learn about calling in the other direction, from .NET clients to COM servers.
Although COM clients can call code that is exposed in a public class by .NET servers, .NET code is not directly accessible to COM clients. In order to use .NET code from a COM client, you need to create a proxy known as a COM callable wrapper (CCW). In this section, you'll learn about the CCW architecture, as well as the steps necessary to create and deploy a .NET class to be used by COM clients.
COM Callable Wrappers
Code that operates within the .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. Because COM was designed before the CLR existed, and COM code does not operate within the infrastructure provided by the CLR, it can't use any of the CLR services. All of your COM components are, by definition, unmanaged code.
Managed code components not only depend on the CLR, they require the components with which they interact to depend on the CLR. Because COM components don't operate within the CLR, they are unable to call managed code components directly. The unmanaged code simply cannot reach into the CLR to directly call managed components.
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 managed code from unmanaged code is known as a COM callable wrapper, or CCW. Figure 1 shows schematically how CCWs straddle the boundary between managed and unmanaged code. This figure includes a COM program named ComUI.exe, two .NET components named NETService.dll and Utility.dll, and the necessary technology that connects them.
Figure 1. Calling managed code with CCWs
Prerequisites for COM Callable Classes
There are two prerequisites to keep in mind when creating a .NET class that will be used by COM clients.
First, explicitly define an interface in your Visual Basic .NET code, and have the class implement the interface. For example, this code snippet defines an interface named iFile, and a class that implements the interface:
Public Interface iFile Property Length() As Integer End Interface Public Class TextFile Implements iFile ' details omitted End Class
Implementing functionality through interfaces has a major benefit for COM clients. .NET keeps interfaces consistent with previous versions when generating CCWs. This helps keep changes to your .NET server from breaking COM clients.
Secondly, any class that is to be visible to COM clients must be declared public. The tools that create the CCW only define types based on public classes. The same rule applies to methods, properties, and events that will be used by COM clients.
You should also consider signing .NET assemblies containing classes that will be used by COM with a cryptographic key pair. Microsoft refers to this as signing the assembly with a strong name. Signing an assembly with a strong name helps .NET ensure that the code in the assembly has not been changed since the assembly was published. This is a requirement for all global assemblies, which are assemblies that are to be shared by multiple clients, although unsigned assemblies can also be called by COM clients.
Note It is possible to use an unsigned assembly from a COM client by deploying the assembly directly to the COM client's directory as a private assembly. This document does not cover the private assembly approach, because global assemblies are more compatible than private assemblies with the architecture of most COM applications.
It's good practice to sign all assemblies, even private assemblies. This will help in generating better CLSIDs for managed classes, and help avoid collisions between classes in different assemblies.
To create a strong name, you can use the sn tool. There are many options for this command-line tool, and you can type sn /? at a command prompt to see them all. The option you need for signing an assembly is -k, which creates a key file. By default, key files use the extension .snk. For example, to create a key file named NETServer.snk, you could use this command line:
sn -k NETServer.snk
Deploying an Application for COM Access
After you've created a .NET assembly containing a class that will be called by a COM client, there are three steps to make the class available to COM.
First, you must create a type library for the assembly. A type library is the COM equivalent of the metadata contained within a .NET assembly. Type libraries are generally contained in files with the extension .tlb. A type library contains the necessary information to allow a COM client to determine which classes are located in a particular server, as well as the methods, properties, and events supported by those classes. The .NET Framework SDK includes a tool named tlbexp (type library exporter) that can create a type library from an assembly. Tlbexp includes a number of options, and you can type tlbexp /? at a command prompt to see all of them. One of these options is the /out option, which lets you specify the name of the generated type library. (One is created for you if you don't choose to create your own name.) For example, to extract the metadata from an assembly named NETServer.dll to a type library named NETServer.tlb, you could use this command line:
tlbexp NETServer.dll /out:NETServer.tlb
Secondly, you should use the Assembly Registration Tool (regasm) from the .NET Framework SDK to both create the type library and register it in a single operation. This is the easiest tool to use when you're doing both .NET and COM development on a single computer. Like tlbexp, there are a number of options for regasm; type regasm /? at a command prompt to see them all. To create and register a type library using regasm, use a command line such as this:
regasm /tlb:NETServer.tlb NETServer.dll
Thirdly, you must install the .NET assembly into the Global Assembly Cache (GAC) so that it will be available as a shared assembly. To install an assembly into the GAC, use the gacutil tool:
gacutil /i NETServer.dll
Again, you can get a list of all the options for gacutil by typing gacutil /? at a command prompt.
In the following example, you will use properties and methods in a .NET component from COM code. You will use regasm to create a type library from the .NET assembly and to register an assembly, and you will use gacutil to make the assembly globally available. You'll then see how you can use this .NET assembly from within Visual Basic 6.0 COM code.
Create the .NET Assembly
To create the .NET assembly containing a public class, follow these steps:
- Open Microsoft® Visual Studio® .NET and click New Project on the Start Page.
- Select Visual Basic Project from the tree view on the left-hand side of the screen.
- Select Class Library as the project template.
- Set the name of the application to PhysServer2 and click OK to create the project.
- Highlight the class called Class1.vb in the Solution Explorer window and rename it to NETTemperature.vb.
- Select the code for Class1 in NETTemperature.vb (this will be an empty class definition) and replace it with the following code:
Public Interface iTemperature Property Celsius() As Double Property Fahrenheit() As Double Function GetCelsius() As Double Function GetFahrenheit() As Double End Interface Public Class NET_Temperature Implements iTemperature Private mdblCelsius As Double Private mdblFahrenheit As Double Public Property Celsius() As Double _ Implements iTemperature.Celsius Get Celsius = mdblCelsius End Get Set(ByVal Value As Double) mdblCelsius = Value mdblFahrenheit = ((Value * 9) / 5) + 32 End Set End Property Public Property Fahrenheit() As Double _ Implements iTemperature.Fahrenheit Get Fahrenheit = mdblFahrenheit End Get Set(ByVal Value As Double) mdblFahrenheit = Value mdblCelsius = ((Value - 32) * 5) / 9 End Set End Property Public Function GetCelsius() As Double _ Implements iTemperature.GetCelsius GetCelsius = mdblCelsius End Function Public Function GetFahrenheit() As Double _ Implements iTemperature.GetFahrenheit GetFahrenheit = mdblFahrenheit End Function End Class
This code starts by defining an interface named iTemperature. Because the interface is defined with the Public keyword, it will be exported to the type library you'll create from this assembly. You can think of an interface definition as the skeleton for all or part of a class definition. The interface definition can contain members (properties, methods—either functions or subs—and events), just like a class. But unlike a class, an interface definition contains no code for any of these members. A class can implement one interface (as in this example) or more than one interface.
This code defines the NET_Temperature class that uses the interface iTemperature. In particular, this line in the class definition sets up a contract between the class and the interface:
The contract says that the class will implement all of the members of the interface. It may also contain additional members that are not part of the interface, but you'll get an error if you try to build a class that does not completely implement an interface.
Note The class exposes two public properties and two public methods. (For the basics of creating classes, methods, and properties, see Creating Classes in Visual Basic .NET).
Notice the syntax used to associate members in the class with members of the interface that the class implements. For example, the Celsius property in the NET_Temperature class is defined this way:
Public Property Celsius() As Double _ Implements iTemperature.Celsius
That line of code defines a property that returns a Double, and tells the compiler that this property is an implementation of the Celsius property within the iTemperature interface.
Create a Key Pair and Sign the Assembly
To make the assembly globally available, you need to create a key pair and use it to sign the assembly. In addition, you can make it easier to work with the assembly by adding a title and description.
To sign the assembly, you can run the sn utility and add the name of the key file by hand, or you can generate a strong name using the Visual Studio .NET user interface. We'll use the latter method. To do so, follow these steps:
- In the Solution Explorer in Visual Studio .NET, double-click the AssemblyInfo.vb file to open it in the editing window.
- In the Assembly Attributes section at the top of this file, modify the AssemblyTitle and AssemblyDescription lines to read:
<Assembly: AssemblyTitle("PhysServer2")> <Assembly: AssemblyDescription(".NET Version of PhysServer")>
Tip! Assembly Attributes in Visual Basic .NET are the equivalent of Project Properties in Visual Basic 6.0.
- In the Solution Explorer, right-click the project node and choose Properties. Click the Common Properties folder and then the Strong Name property page. Select the box labeled Generate Strong Name Using. Click Generate Key to generate a key file and add it to your project. Click OK to close the property dialog box.
Now you're ready to create the assembly. Click Build or press Ctrl+Shift+B to build the assembly.
Register the Assembly and Create a Type Library
At this point, you could use the new assembly and the NET_Temperature class from another .NET application. But, you still have to make the class and its members available to your COM applications. Open a Visual Studio .NET command prompt (Click Start, then Programs, then Microsoft Visual Studio .NET 7.0, then Visual Studio .NET Tools, and then Visual Studio .NET Command Prompt), change to the project directory for PhysServer2, and type:
regasm /tlb:PhysServer2.tlb PhysServer2.dll
The regasm utility will create a type library and register it in your Windows registry to make the classes in PhysServer2.dll available to COM clients.
Add the Assembly to the Global Assembly Cache
Finally, to make the newly registered assembly globally available to all COM clients wherever they're located on your hard drive, switch back to the Visual Studio .NET command prompt and type:
gacutil /I PhysServer2.dll
The gacutil utility will add the assembly to the GAC and print a status message to tell you that it's done so.
Write Visual Basic 6.0 Code to Call the .NET Class
Now you're ready to write a COM client to use the NET_Temperature class. Follow these steps:
- Open Visual Basic 6.0 and in the New Project dialog box, click New tab.
- Select Standard EXE and click Open.
- Highlight the form called Form1 in the Project Explorer window and rename it to frmTemperature.
- Create the form shown in Figure 2 by adding the appropriate controls and setting the properties of those controls, as outlined in Table 1.
Table 1. Controls for frmTemperature
Control Type Property Value Label Name lblFahrenheit Caption Fahrenheit TextBox Name txtFahrenheit Text (blank) Button Name cmdConvertToC Caption Convert to C Label Name lblCelsius Caption Celsius TextBox Name txtCelsius Text (blank) Button Name cmdConvertToF Caption Convert to F
Figure 2. The test form design
- To use the class in PhysServer2 through the CCW, click Project, and then click References to open the References dialog box. Select the reference for the .NET Version of PhysServer, as shown in Figure 3. Click OK to close the dialog box.
Figure 3. Setting a reference to the .NET component
Now you're ready to write code that uses the methods and properties of the NET_Temperature class. On the View menu, click Code, and enter this code in the form module for frmTemperature:
Private moTempClass As PhysServer2.NET_Temperature Private moTemp As PhysServer2.iTemperature Private Sub cmdConvertToC_Click() With moTemp .Fahrenheit = txtFahrenheit.Text txtCelsius.Text = .GetCelsius End With End Sub Private Sub cmdConvertToF_Click() With moTemp .Celsius = txtCelsius.Text txtFahrenheit.Text = .GetFahrenheit End With End Sub Private Sub Form_Load() Set moTempClass = New PhysServer2.NET_Temperature Set moTemp = moTempClass End Sub
Remember, in the .NET project you defined the Net_Temperature class using the iTemperature interface. This code demonstrates how you can retrieve the interface (represented as an object named moTemp) back from the object. Although this may look like extra code, if you experiment in Visual Basic, you'll find that it's much more convenient to work with the interface than it is with the object. That's because the interface supports Microsoft® IntelliSense® command completion.
Try It Out
To see the NET_Temperature class in action, follow these steps:
- Press F5 to start the project.
- Enter 95 in the Fahrenheit textbox and click Convert to C. The Celsius box should fill in with the value 35.
- Enter -14 in the Celsius box and click Convert to F. The Fahrenheit box should fill in with the value 6.8.
- Close the form to stop the project.
Of course, the entire process of calling .NET components from Visual Basic 6.0 is new, because .NET didn't exist when Visual Basic 6.0 was released. Visual Basic 6.0 does have the ability to create multiple components connected by COM calls, and the .NET CCWs make the .NET calls function like COM calls. The nice thing about the whole process is that you don't need to risk the safety and stability of .NET components to use them from COM code. By calling through the CLR, the CCWs still give your .NET components all the benefits of managed code, no matter where you use the .NET components.
Although .NET is an entirely new development environment, the designers did not ignore the issue of compatibility with existing code. By properly constructing your .NET components and using tools such as sn, tlbexp, regasm, and gacutil, you can expose classes from a .NET assembly to COM clients.
Calling a .NET component from a COM component is not a trivial exercise. As you've seen in this document, you need to make explicit code modifications to your .NET component to enable this scenario. But the modifications are minor, and there are certainly benefits from enabling COM clients to call .NET servers. If you're migrating a complex application from COM to .NET one component at a time, you'll find the techniques outlined in this document essential.
About the Author
Mike Gunderloy writes about software and raises chickens in eastern Washington State. He's the 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 © 2002 Informant Communications Group and Microsoft Corporation
Technical editing: PDSA, Inc. and KNG Consulting