Friday, October 21, 2011
Calling a .NET Component from a COM Component
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 iscomponentized. 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.
- 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.
Creating .NET Classes for Use in COM Applications
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
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
regasm /tlb:NETServer.tlb NETServer.dll
gacutil /i NETServer.dll
Practice Calling a .NET Component From COM
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 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:
Implements iTemperature
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
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 labeledGenerate 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.
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.dllThe 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.dllThe 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
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
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.
What's New Since Visual Basic 6.0?
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.Summary
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.