http://msdn.microsoft.com/library/de...llnetfrcom.asp
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.
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.
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
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.
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:
1. Open Microsoft® Visual Studio® .NET and click New Project on
the Start Page.
2. Select Visual Basic Project from the tree view on the left-hand
side of the screen.
3. Select Class Library as the project template.
4. Set the name of the application to PhysServer2 and click OK to
create the project.
5. Highlight the class called Class1.vb in the Solution Explorer
window and rename it to NETTemperature.vb.
6. 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:
Implements iTemperature
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:
1. In the Solution Explorer in Visual Studio .NET, double-click the
AssemblyInfo.vb file to open it in the editing window.
2. 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.
3. 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:
1. Open Visual Basic 6.0 and in the New Project dialog box, click
New tab.
2. Select Standard EXE and click Open.
3. Highlight the form called Form1 in the Project Explorer window
and rename it to frmTemperature.
4. 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
5. 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:
1. Press F5 to start the project.
2. Enter 95 in the Fahrenheit textbox and click Convert to C. The
Celsius box should fill in with the value 35.
3. Enter -14 in the Celsius box and click Convert to F. The
Fahrenheit box should fill in with the value 6.8.
4. 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.