Using COM Components
Access unmanaged code from a Windows service, a serviced component, a .NET Remoting object, and an XML Web service.
You might already have a lot of development done in your organization that you would like to reuse with .NET development as you slowly migrate toward it. Fortunately, if your old programs use COM architecture, you don't have to do a "big bang" migration all at once. .NET components can call COM components, and COM components can call .NET components. This means that you can migrate one component (a control, a class library, and so on) at a time, and still keep all your code working together.
NOTE
ActiveX Controls
Prior to .NET, ActiveX was a major means of delivering encapsulated functionality such as controls to the Windows applications. I'll not be discussing ActiveX in this book because it is a client-side technology and has little use for creating server-side applications and components. For more information on using ActiveX from .NET applications, refer to my book, MCAD/MCSD Training Guide: Exam 70-316, Developing and Implementing Windows-based Applications with Visual C# .NET and Visual Studio .NET.
Why might you want to undertake such a gradual migration? There are four basic reasons for maintaining part of a system in COM components while moving other parts to .NET components:
It takes time to learn enough about Visual C# .NET and the .NET Framework to be productive. While you're making your way up the learning curve, you may have to continue development of existing COM components. You may have components that can't be easily moved to .NET because they use language features that are no longer supported or because of other implementation quirks. It takes time to move code from one system to another. Unless you can afford extended downtime, a gradual move lets you write the converted code at a slower pace. Your application may depend on third-party controls or libraries for which you do not have the source code.
In the following sections you'll learn how to encapsulate COM components for use with .NET applications. There are both command-line and GUI tools for working with COM components. Before I talk about those tools, though, you should know a bit more about wrapper classes.
Understanding Runtime Callable Wrappers
As you probably already know, Visual C# .NET creates code that operates within the .NET CLR. Code that operates within the CLR is called managed code. Managed code benefits from the services that the CLR offers, including garbage collection, memory management, and support for versioning and security.
Code that does not operate within the CLR is called unmanaged code. Code that was created by tools that are older than the .NET Framework is by definition unmanaged code. COM components are unmanaged code because COM was designed before the CLR existed, and COM components don't make use of any of the services of the CLR.
Managed code expects that all the code with which it interacts will use the CLR. This is an obvious problem for COM components. How can you take a component that was developed before the advent of .NET and make it look like a .NET component to other .NET components? The answer is to use a proxy. In general terms, a proxy accepts commands and messages from one component, modifies them, and passes them to another component. The particular type of proxy that allows you to use COM components within a .NET application is called a runtime callable wrapper (RCW). That is, it's a proxy that can be called by the CLR.
Figure shows schematically how the pieces fit together.

Building a COM DLL
To see how COM interoperability works, you need a COM library. Step-by-Step 8.1 shows how to build a simple one.
|
8.1 Building a COM Dynamic Link Library (DLL)
Launch Visual Basic 6. Create a new ActiveX DLL project. Select the Project1 node in the Project Explorer window and rename it MyCustomer. Select the Class1 node in the Project Explorer window and rename it Balances. Add this code to the Balances class:
Option Explicit
Private mintCustomerCount As Integer
Private macurBalances(1 To 10) As Currency
' Create a read-only CustomerCount property
Public Property Get CustomerCount() As Integer
CustomerCount = mintCustomerCount
End Property
' Create a GetBalance method
Public Function GetBalance( _
CustomerNumber As Integer) As Currency
GetBalance = macurBalances(CustomerNumber)
End Function
' Initialize the data
Private Sub Class_Initialize()
Dim intI As Integer
mintCustomerCount = 10
For intI = 1 To 10
macurBalances(intI) = _
Int(Rnd(1) * 100000) / 100
Next intI
End Sub
NOTE
If You Don't Have Visual Basic
Even if you don't have Visual Basic, you can still test COM interoperability by working with a COM library that's already installed on your computer. A variety of Microsoft components, including Office, SQL Server, and ADO, install COM libraries. Save the Visual Basic project. Select File, Make MyCustomer.dll to create the COM component.
|
Registering a COM DLL
When you use Visual Basic 6 to build the MyCustomer.dll—as you did in Step-by-Step 8.1—the COM DLL is automatically registerered in the Windows Registry.
If you are using the code files from the CD or if you have an unregistered COM DLL, you need to register the COM DLL in the Windows Registry before you can use it in .NET code. You can register by using regsvr32.exe in the command prompt as shown here:
regsvr32 MyCustomer.dll
Using the Type Library Importer Tool (tlbimp.exe)
The task of using COM components from the .NET Framework is substantially facilitated by the fact that COM components, like .NET components, have metadata that describe their interfaces. For .NET components, the metadata is embedded in the assembly manifest. For COM components, the metadata is stored in a type library. A type library can be a separate file, or (as with Visual Basic 6 class libraries) it can be embedded within another file.
The .NET Framework includes a tool, the Type Library Importer tool (tlbimp.exe), that can create an RCW from COM metadata contained in a type library, as seen in Step-by-Step 8.2.
|
8.2 Using the Type Library Importer Tool
Launch a .NET command prompt by selecting Start, Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, Visual Studio .NET Command Prompt. Inside the command prompt window, navigate to the folder that contains the MyCustomer.dll COM library. Enter this command line to run the Type Library Importer tool:
tlbimp MyCustomer.dll /out:NETMyCustomer.dll
Launch Visual Studio .NET. Create an ASP.NET Web service application named 320C08. Right-click the References node in Solution Explorer and select Add Reference. Click the Browse button in the Add Reference dialog box. Browse to the NETMyCustomer.dll file that you created in step 3. Click OK to add the reference to the project. Right-click the Service1.asmx file in Solution Explorer and rename it StepByStep8_2.asmx. Click the hyperlink on the StepByStep8_2.asmx design view to switch to the code view. Change all the occurrences of Service1 to refer to StepByStep8_2. Add the following WebService attribute before the StepByStep8_2 class definition:
[WebService(Namespace="http://NetExam.org/Balance")]
public class StepByStep8_2 :
System.Web.Services.WebService
Add the following Web method definition to the Web service:
[WebMethod]
public decimal RetrieveBalance(short custNumber)
{
NETMyCustomer.Balances b =
new NETMyCustomer.Balances();
return b.GetBalance(ref custNumber);
}
Set the Web service as the start page for the project. Run the project. You should see that a browser is launched showing the test page. Click on the RetrieveBalance method link. Enter a number between 1 and 10 for the custNumber parameter and click the Invoke button. A second browser window opens with that customer's balance.
|
In Step-by-Step 8.2, you use the Type Library Importer tool to create an RCW for the COM type library. This RCW is a library that you can add to your .NET project as a reference. After you do that, the classes in the COM component can be used just like native .NET classes. When you use a class from the COM component, .NET makes the call to the RCW, which in turn forwards the call to the original COM component and returns the results to your .NET managed code.
The Type Library Importer tool supports the command-line options listed in Figure.
Command-Line Options for the Type Library Importer Tool|
/asmversion:versionNumber | Specifies the version number for the created assembly | /delaysign | Prepares the assembly for delay signing | /help | Displays help for command-line options | /keycontainer:containerName | Signs the assembly with the strong name from the specified key container | /keyfile:filename | Specifies a file containing public/private key pairs that is used to sign the resulting file | /namespace:namespace | Specifies the namespace for the created assembly | /out:filename | Specifies the name of the created assembly | /primary | Produces a primary interop assembly | /publickey:filename | Specifies the file containing a public key that is used to sign the resulting file | /reference:filename | Specifies a file to be used to resolve references from the file being imported | /silent | Suppresses information that would otherwise be displayed on the command line during conversion | /strictref | Refuses to create the assembly if one or more references cannot be resolved | /sysarray | Imports COM SAFEARRAY as instances of the System.Array type | /unsafe | Creates interfaces without the .NET Framework security checks | /verbose | Displays additional information on the command line during conversion | /? | Displays help about command-line options |
EXAM TIP
Options Overview
You don't need to memorize all the options for the Type Library Importer tool. You should know that most of the options deal with the security of the resulting RCW and the code that it contains.
Assigning a Strong Name to a COM DLL
If you want to use a COM DLL from a serviced component or if you want to place the COM DLL in the global assembly cache, then you must assign a strong name to the COM DLL. To assign a strong name using a public/private key pair, you use the /keyfile option of the Type Library Importer tool (tlbimp.exe).
Using COM Components Directly
The Visual Studio .NET interface provides a streamlined way to use a COM component from your .NET code, as shown in Step-by-Step 8.3.
|
8.3 Using a Direct Reference with a COM Library
Add a Web service page named StepByStep8_3.asmx to the project. Right-click the References node in Solution Explorer and select Add Reference.
NOTE
COM DLL Needs to Be Registered Before It Is Used
MyCustomer.dll will not be in the COM tab of the Add Reference dialog box if it was not built in Step-by-Step 8.1. Refer to the section "Registering a COM DLL," earlier in this chapter, on how to register a COM DLL. Select the COM tab in the Add Reference dialog box. Scroll down the list of COM components until you come to the MyCustomer library. Select the MyCustomer library, click Select, and then click OK. Add the following Web method definition to the Web service:
[WebMethod]
public decimal RetrieveBalance(short custNumber)
{
MyCustomer.Balances b =
new MyCustomer.Balances();
return b.GetBalance(ref custNumber);
}
Set the Web service as the start page for the project. Run the project. You should see that a browser is launched showing the test page. Click on the RetrieveBalance method link. Enter a number between 1 and 10 for the custNumber parameter and click the Invoke button. A second browser window opens with that customer's balance.
|
When you directly reference a COM library from the Visual Studio .NET Integrated Development Environment (IDE), the effect is almost the same as if you used the Type Library Importer tool to import the same library. Visual Studio .NET creates a new namespace with the name of the original library and then exposes the classes from the library within that namespace.
Although you can use either of the two methods described in this chapter to call a COM component from a .NET component, there are reasons to prefer one method over the other:
For a COM component that will only be used in a single Visual C# .NET project and that you wrote yourself, use the easiest method: direct reference from the .NET project. This method is suitable only for a truly private component that does not need to be shared by the other projects. If a COM component is shared among multiple projects, use the Type Library Importer tool so that you can sign the resulting assembly and place it in the global assembly cache (GAC). Shared code must be signed with a strong name. WARNING
Import Only Your Own Code
You should not use either of the import methods on code that is written by another developer. You are not allowed to sign code that is written by someone else. If you need to use a COM component from another developer, you should obtain a Primary Interop Assembly (PIA) from the component's original developer. Microsoft supplies PIAs for all its own common libraries. For example, there are PIAs for almost all the components of Microsoft Office, available at msdn.microsoft.com/downloads/default.asp?url=/downloads/sample.asp?url=/msdn-files/027/001/999/msdncompositedoc.xml.
If you need to control details of the created assembly—such as its name, namespace, or version number—you must use the Type Library Importer tool. The direct reference method gives you no control over the details of the created assembly.
|
The goal of this exercise is to compare the performance of two implementations of the same code, using a COM library for one implementation and a native .NET class for the other implementation. You should choose some code that takes a reasonably long time to run so that you can detect any differences between the two implementations.
How would you create such an application?
EXAM TIP
Timing Code
To tell how long a piece of code takes to run, you can use arithmetic with two instances of the DateTime class to produce a TimeSpan object.
You should try working through this problem on your own first. If you get stuck, or if you'd like to see one possible solution, follow these steps:
Launch Visual Basic 6. Create a new ActiveX DLL project. Select the Project1 node in the Project Explorer window and rename it Numeric. Select the Class1 node in the Project Explorer window and rename it Primes. Add this code to the Primes class:
Option Explicit
Public Function HighPrime(Max As Long) As Long
Dim a() As Byte
Dim lngI As Long
Dim lngJ As Long
ReDim a(Max)
' In the array, 1 indicates a prime,
' 0 indicates nonprime.
' Start by marking multiples
' of 2 as nonprime
For lngI = 0 To Max
If lngI Mod 2 = 0 And lngI <> 2 Then
a(lngI) = 0
Else
a(lngI) = 1
End If
Next lngI
' Now execute the usual sieve
' of erasthones algorithm
For lngI = 3 To Sqr(Max) Step 2
If a(lngI) = 1 Then
' This is a prime,
' so eliminate its multiples
For lngJ = lngI + lngI To Max _
Step lngI
a(lngJ) = 0
Next lngJ
End If
Next lngI
' Find the largest prime by working backwards
For lngI = Max To 1 Step -1
If a(lngI) = 1 Then
HighPrime = lngI
Exit For
End If
Next lngI
End Function
Save the Visual Basic project. Select File, Build Numeric.dll to create the COM component. In your Visual C# .NET project, right-click the References node of Solution Explorer and select Add Reference. Select the COM tab in the Add Reference dialog box. Scroll down the list of COM components until you come to the Numeric library. Select the Numeric library, click Select, and then click OK. Add a new class to your Visual C# .NET project. Name the class Primes.cs. Add this code to the Primes.cs class:
using System;
public class Primes
{
public int HighPrime(int max)
{
Byte[] a = new Byte[max];
Int32 intI;
Int32 intJ;
// In the array, 1 indicates a prime,
// 0 indicates nonprime. Start by marking
// multiples of 2 as nonprime
for(intI = 0;intI < max; intI++)
{
if((intI % 2 == 0) && (intI != 2))
a[intI] = 0;
else
a[intI] = 1;
}
// Now execute the usual sieve
// of erasthones algorithm
for(intI = 3; intI <= System.Math.Sqrt(max);
intI=intI+2)
{
if (a[intI] == 1)
{
// This is a prime,
// so eliminate its multiples
for(intJ = intI + intI; intJ < max;
intJ=intJ+intI)
a[intJ] = 0;
}
}
// Find the largest prime by working backward
for(intI = max-1; intI > 0; intI--)
{
if (a[intI] == 1)
{
break;
}
}
return intI;
}
}
Add a new Web service named GuidedPracticeExercise8_1.asmx to your Visual C# .NET project. Switch to code view and add the following Web method definitions to the Web service definition:
[WebMethod]
public string CalculateCOMHighPrime(int maxNumber)
{
Numeric.Primes COM_Primes = new Numeric.Primes();
DateTime dt = DateTime.Now;
Int32 intHighPrime =
COM_Primes.HighPrime(ref maxNumber);
TimeSpan ts = DateTime.Now.Subtract(dt);
string COMResults = "High prime = " +
intHighPrime.ToString() + " took " +
ts.Ticks.ToString() + " ticks";
return COMResults;
}
[WebMethod]
public string CalculateNETHighPrime(int maxNumber)
{
Primes NET_Primes = new Primes();
DateTime dt = DateTime.Now;
Int32 intHighPrime =
NET_Primes.HighPrime(maxNumber);
TimeSpan ts = DateTime.Now.Subtract(dt);
string NETResults = "High prime = " +
intHighPrime.ToString() + " took " +
ts.Ticks.ToString() + " ticks";
return NETResults;
}
WARNING
The Pitfalls of Performance
In Guided Practice Exercise 8.1, the .NET class was much faster than the COM class. But timing performance on Windows is notoriously difficult—for several reasons. First, although you can measure things down to the timer tick, the hardware does not provide precise-to-the-tick numbers. Second, because of caching and because other programs are in memory, timings tend not to be repeatable. Finally, it's hard to write exactly equivalent COM and .NET code. Nevertheless, repeated runs of a program such as this example can give you general information on which of two alternatives is faster. Set the Web service as the start page for the project. Run the project. You should see that a browser is launched showing the test page. Click on the CalculateCOMHighPrime method link. Enter a fairly large number in the maxNumber parameter and click the Invoke button. The code finds the largest prime number that is smaller than the number you entered by using the COM library, and it displays the results as shown in Figure.

Click the Back button in the main page and then click on the CalculateNETHighPrime method link. Enter the same large number that you previously entered for the COM implementation in the maxNumber parameter and click the Invoke button. The code finds the largest prime number that is smaller than the number you entered using the native .NET class and displays the results as shown in Figure.
If you have difficulty following this exercise, review the section "Using COM Components" earlier in this chapter. After doing that review, try this exercise again. |
Using COM components from .NET managed code requires the creation of an RCW. You can create an RCW for a COM component by using the Type Library Importer tool or by directly referencing the COM component from your .NET code. To use COM components that you did not create, you should obtain a PIA from the creator of the component. RCWs impose a performance penalty on COM code.
|
|