The .NET Framework presents exciting new opportunities for developers.

By now, you may have heard that .NET represents a departure from COM, the focal point of Windows development for the past several years. Because of the investments in COM, it is quite likely you will want to implement COM in VS .NET. Conversely, the .NET Framework has a number of nice features that you will want to implement in COM-based applications. This article illustrates how COM and the .NET Framework can work together.

The successful developer will not view events in terms of .NET vs. COM. Instead, the successful developer will learn to leverage .NET in COM and COM in .NET.

The .NET Framework provides a set of tools and services known as COM Interop. These tools facilitate calling COM objects from .NET applications and .NET Framework classes from COM. The good news is that as a developer, you can concentrate on working with your components as opposed to spending lots of unproductive time in figuring out how to get everything to work. In simple terms, when you are working in .NET and you add a reference to a COM component, .NET does the work of creating the COM Interop layer that facilitates working with the COM component in managed code. If you need to expose your .NET classes to COM, when you build your classes in .NET, you can elect to build type libraries and register your classes so that to the COM world, your .NET classes act and behave like COM components. This article demonstrates using COM Interop within .NET applications and legacy COM applications.

Invoking COM in .NET

Working with COM components in VS .NET works in much the same manner as it did in Visual Basic 6. Like VB, you need to establish a reference to a COM class. In order to establish a COM reference, you need to select a COM component that is already registered on your system. To establish the reference, you need to select Add Reference from the Project Menu. The Add Reference dialog is illustrated in Figure 1.

Figure 1: The Add Reference dialog in VS .NET allows you to add .NET or COM class references to your project.
Figure 1: The Add Reference dialog in VS .NET allows you to add .NET or COM class references to your project.

Once you have added the reference, working with COM components in VS .NET is as simple as it was in Visual Studio 6. To illustrate just how easy it is to work with COM in VS .NET, let's use one of the most common pieces of software in use today: Microsoft Outlook. The following simple example illustrates the following:

  • Creating a simple VB .NET Windows application
  • Adding a reference to the Outlook Object Library
  • Displaying a new e-mail item

Creating a Simple VB .NET Windows Application

The first order of business is to create a single-form VB .NET Windows application. The name of the project, which is included in the files that can be downloaded, is called dotnet2com. Figure 2 illustrates the .NET design surface for this project.

Figure 2: The dotnet2com project contains a single form that launches a new Outlook email item.
Figure 2: The dotnet2com project contains a single form that launches a new Outlook email item.

Once the form and a few buttons have been created, the next step involves adding a reference to the Outlook Object Library.

Adding a Reference to and Working with the Outlook Object Library

Figure 1 illustrates the Add Reference dialog that is used to add a reference to the Outlook Object Library. Once the reference has been added, additional entries will be made to the references section of the Solution Explorer. The Solution Explorer appears in the far right-hand portion of Figure 2.

Once the project has a reference to Outlook, you can begin to incorporate Outlook functionality into your .NET project. Figure 3 illustrates how IntelliSense works in the same manner it did in Visual Studio 6.

Figure 3: IntelliSense works with COM Components in VS .NET in the same manner as it does in Visual Studio 6.
Figure 3: IntelliSense works with COM Components in VS .NET in the same manner as it does in Visual Studio 6.

The code in Listing 1 is attached to the Click() of the Send Mail Button of the form illustrated in Figure 2:

Listing 1: Code to send e-mail with Outlook

' Create an instance of Outlook
Dim oOutlook As New Outlook.Application()

' Create a variable to hold the new mail item
Dim myitem As Outlook.MailItem

' Create the new mail item
myitem = oOutlook.CreateItem(Outlook.OlItemType.olMailItem)

' Display the mail UI so that user can createthe email message
myitem.Display()

With the references and code out of the way, the fun part can begin; working with Outlook! Figure 4 illustrates the results of a few lines of code.

Adding a reference to and working with a custom COM Component

Chances are good that you or your clients have an investment in a number of COM components. Can those work with .NET as well? The answer is a resounding yes! Using the same techniques that have already been introduced in this article, the following code calls a COM object built in the Visual FoxPro language.

Figure 4: With a few lines of code, it is easy to incorporate Outlook email capabilities into your .NET applications!
Figure 4: With a few lines of code, it is easy to incorporate Outlook email capabilities into your .NET applications!

The following code defines a simple Visual FoxPro class that returns a unique file name:

Define Class vfp as Custom Olepublic
   Function GetFileName() as String
      Return "_" + Sys(3)
   EndFunc
EndDefine

Figure 5 illustrates the VS .NET Add References dialog with the VFP class selected.

Figure 5: Custom COM Classes can also be referenced in VS .NET Applications.
Figure 5: Custom COM Classes can also be referenced in VS .NET Applications.

The following code, which has been attached to the Click() of another button, instantiates and invokes the Visual FoxPro COM component in VS .NET:

' Create an instance of Visual FoxPro COM Component
Dim oVFP As New vfpcomtest.vfpClass()
' Display a messagebox dialog, displaying a file name
 MessageBox.Show(oVFP.GetFileName(), "File Name from VFP!")

Figure 6 illustrates how the Visual FoxPro component operates in VS .NET.

Invoking .NET in COM

Figure 6: The file name displayed in the MessageBox Dialog in VS .NET was generated by a Visual FoxPro COM Component.
Figure 6: The file name displayed in the MessageBox Dialog in VS .NET was generated by a Visual FoxPro COM Component.

If you were impressed with how simple it is to invoke COM components in .NET, you are going to be even more impressed with the converse scenario: calling .NET components in COM. A basic question that should be addressed is, “Why would you invoke .NET Components in COM?” The fact is, COM is going to be around for a long time. The environment has served the developers well for a long time and, quite frankly, just because .NET is the new kid on the block, it does not necessarily mean that a COM-based application will be rewritten in .NET. At the same time, .NET has a lot of nice components that interact with Windows that you may want to leverage in an existing COM-based application.

VS .NET ships with a number of handy components that allow applications to easily interact with the Windows operating system. These components include the following:

  • Event Logging
  • Message Queuing
  • Windows Services
  • File System Watcher
  • Performance Monitor

Creating custom performance monitor categories and counters and being able to interact with the Windows Performance Monitor application has never been a straightforward process. Perhaps you have wanted to use the performance monitor in conjunction with your application. With VS .NET, the task has become a lot easier. To illustrate how you can leverage the Performance Counter .NET class in COM, let's explore a VB .NET class that encapsulates the Performance Counter class.

VB .NET Performance Monitor Class

The following VB .NET code (Listing 2) provides a wrapper around the .NET Framework PerformanceCounter() class:

Listing 2: Wrapper code for the PerformanceCounter Class

Public Class PerformanceMonitor
    ' Create private member to hold 
    ' instance of performance counter class
    Private counter As New System.Diagnostics.PerformanceCounter()
    
    ' Public method to initialize counter
    ' with a category and counter name as 
    ' well as the amount to increment the 
    ' counter by. In addition, the read only
    ' flag is toggled. If you want to be able
    ' to update the counter, readonly must be
    ' set to False. 
    Public Sub InitializeCounter(ByVal strcategoryname As String, _
                                 ByVal strcountername As String, _
                                 ByVal breadonly As Boolean, _
                                 ByVal iIncrement As Int16)
        With counter
            .CategoryName = strcategoryname
            .CounterName = strcountername
            .ReadOnly = breadonly
            .IncrementBy(iIncrement)
        End With
    End Sub

    ' The following methods act as public 
    ' interfaces to the private counter
    ' member. 
    Public Sub resetcounter()
        counter.RawValue = 0
    End Sub

    Public Sub incrementcounter()
        counter.Increment()
    End Sub

    Public Sub decrementcounter()
        counter.Decrement()
    End Sub

    Public Function GetCount() As Int16
        Return counter.RawValue
    End Function
End Class

The class is very simple. It holds a private instance of the Performance Counter class and exposes some functionality through a few public methods. In this scenario, you can perform the following tasks:

  • Initialize the counter with a category and counter name as well as setting the increment and read only properties of the performance counter class
  • Increment the counter
  • Decrement the counter
  • Reset the counter

Perhaps you will want to know the number of orders taken every hour or the number of times a specific report is run. The uses for a feature like this are endless. With the details of the class out of the way, it is time to build a .NET component.

Creating a VB .NET Class Library Application

You build DLLs in .NET through a Class Library application. When you create a new VS .NET project, regardless of whether you are using VB, C#, or any other language based on the Common Language Runtime, you have the ability to create the same types of projects. This underscores the language/framework independence that exists in VS .NET.

Figure 7 illustrates the Class Library Application project that will be used to create the .NET DLL. Under Configuration Properties, in the Build Section, you can specify if COM Interop registration settings should be made.

Figure 7: In the build properties of a project, you can specify whether COM Interop registry settings should be made.
Figure 7: In the build properties of a project, you can specify whether COM Interop registry settings should be made.

When you choose the COM Interop option for the build configuration you are working with, several additional tasks are performed when your DLL is built. First, a type library file is created that COM uses to interact with the class. Second, entries are made into the Registry. Once these tasks are completed, the .NET class is exposed and available to COM.

The project illustrated in Figure 7 is called PerformanceMonitorClass. The class is named PerformanceMonitor. The ProgID created for COM is PerformanceMonitorClass.PerformanceMonitor. With the COM Interop in place, the .NET DLL can now be used in COM as well as .NET. The next step in this illustration involves building test applications in VB .NET.

Creating a Custom Performance Monitor Category and Counter

Before going any further, you must create a custom performance monitor category and counter. Another handy tool in VS .NET is the Server Explorer. With this tool, you can inspect items specific to a server. One server-specific area is performance monitor categories and counters. In this example, a category called ‘My Custom Performance Counter Category’ and a counter called ‘MyCounter’ are created. In order to create these custom items, you need to navigate to the Performance Counter Section in the Server Explorer. Once you get to the top node, right-click and choose to add a new counter. Figure 8 illustrates the dialog used to add the category and counter.

Figure 8: Using the Server Explorer, custom counter categories and counters can be created.
Figure 8: Using the Server Explorer, custom counter categories and counters can be created.

Using the Performance Counter .NET Class in .NET and COM

Adding a reference to a .NET class in .NET is identical to adding a COM reference in .NET. Each process uses the Add Reference dialog. The only difference is that, when adding a .NET class, you work in the .NET tab as opposed to the COM tab for COM classes. Figure 9 illustrates adding a reference to the PerformanceMonitor VB .NET Class to a C# Windows Application project.

Figure 9: This C# project invokes the services of the PerformanceMonitor VB .NET Class.
Figure 9: This C# project invokes the services of the PerformanceMonitor VB .NET Class.

The following sections outline how the VB .NET Performance Monitor class is implemented and used in C# (Listing 3**),** Visual Basic .NET (Listing 4), Visual Basic 6 (Listing 5) and Visual FoxPro 7 (Listing 6).

Listing 3: C# Code to call the PerformanceCounter Wrapper

private void Form1_Load(object sender,System.EventArgs e)
{
    string lcCategory  = "My Custom Performance Counter Category";
    string lcName = "MyCounter";	 
    counter = new PerformanceMonitorClass.PerformanceMonitor();
    counter.InitializeCounter(lcCategory, lcName, false, 1);
}

private void Button1_Click(object sender,System.EventArgs e)
{
    counter.incrementcounter();
}

private void Button2_Click(object sender,System.EventArgs e)
{
    counter.decrementcounter();
}

private void Button3_Click(object sender,System.EventArgs e)
{
    counter.resetcounter();
}

private void timer1_Tick(object sender,System.EventArgs e)
{
    txtCount.Text = System.Convert.ToString(counter.GetCount());
}

Listing 4: VB.NET code to call the PerformanceCounter Wrapper

Private Sub _
   Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
   Dim lcCategory As String = "My Custom Performance Counter Category"
   Dim lcName As String = "Mycounter"
   counter.InitializeCounter(lcCategory, lcName, False, 1)
End Sub

Private Sub _
   Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
   counter.incrementcounter()
End Sub

Private Sub _
   Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
   counter.decrementcounter()
End Sub

Private Sub _
   Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
   counter.resetcounter()
End Sub

Private Sub _
   Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
   txtCount.Text = counter.GetCount()
End Sub

Listing 5: Visual Basic 6 code to call the PerformanceCounter Wrapper

Dim pmclass As PerformanceMonitorClass.PerformanceMonitor

Private Sub Command1_Click()
   pmclass.incrementcounter
End Sub

Private Sub Command2_Click()
   pmclass.decrementcounter
End Sub

Private Sub Command3_Click()
   pmclass.resetcounter
End Sub

Private Sub Form_Load()
   Set pmclass = New PerformanceMonitorClass.PerformanceMonitor
   With This.pmclass
     .initializeCounter("My Custom Performance Counter Category", "Mycounter", .F., 1)
   EndWith 
End Sub

Private Sub Timer1_Timer()
   txtCount.Text = pmclass.GetCount()
End Sub

Listing 6: Visual FoxPro code to call the PerformanceCounter Wrapper

PROCEDURE Init
   Local lcClass,lcCategory,lcName
   lcClass = "PerformanceMonitorClass.PerformanceMonitor"
   lcCateogry = "My Custom Performance Counter Category"
   lcName = "MyCounter"
   This.pmclass = CREATEOBJECT(lcClass)
   With This.pmclass
      .initializeCounter(lcCategory, lcName, .F., 1)
   EndWith   
 ENDPROC

 PROCEDURE GotFocus
    This.txtCount.Value = This.pmclass.GetCount()
 ENDPROC

 PROCEDURE command1.Click
    Thisform.pmclass.Incrementcounter
 ENDPROC

 PROCEDURE command2.Click
   Thisform.pmclass.Decrementcounter
 ENDPROC

 PROCEDURE command3.Click
    Thisform.pmclass.Resetcounter
 ENDPROC

 PROCEDURE timer1.Timer
    ThisForm.txtCount.Value = ThisForm.pmclass.GetCount()
 ENDPROC

In all material respects, the code is the same regardless of programming environment.

How to get IntelliSense and COM Interop to work together

When you invoke COM Components in .NET, you get all of the IntelliSense features you have been accustomed to working with. Unfortunately, getting IntelliSense to work with .NET classes exposed as COM components is not quite as easy. In order to get IntelliSense to work in this scenario, you have to go through the extra step of defining and implementing a public interface. The following code (Listing 7) listing modifies the Performance Monitor class by defining and implanting an interface:

Listing 7: Interface for the PerformanceCounter Wrapper

Public Interface IPerformanceMonitor
    Sub initializecounter(ByVal strcategoryname As String, _
                          ByVal strcountername As String, _
                          ByVal breadonly As Boolean, _
                          ByVal iIncrement As Int16)
    Sub resetcounter()
    Sub incrementcounter()
    Sub decrementcounter()
    Function GetCount() As Short
End Interface

Public Class PerformanceMonitor
    Implements IPerformanceMonitor

    Private counter As New System.Diagnostics.PerformanceCounter()
    
    Public Sub InitializeCounter(ByVal strcategoryname As String, _
                                 ByVal strcountername As String, _
                                 ByVal breadonly As Boolean, _
                                 ByVal iIncrement As Int16) _
                                 Implements IPerformanceMonitor.initializecounter

        With counter
            .CategoryName = strcategoryname
            .CounterName = strcountername
            .ReadOnly = breadonly
            .IncrementBy(iIncrement)
        End With
    End Sub

     Public Sub resetcounter() Implements IPerformanceMonitor.resetcounter
        counter.RawValue = 0
    End Sub
    
    Public Sub incrementcounter() Implements IPerformanceMonitor.incrementcounter
        counter.Increment()
    End Sub
    
    Public Sub decrementcounter() Implements IPerformanceMonitor.decrementcounter
        counter.Decrement()
    End Sub
    
    Public Function GetCount() As Short Implements IPerformanceMonitor.GetCount
        Return counter.RawValue
    End Function
End Class

Figure 10 illustrates how to get IntelliSense to work with the Performance Monitor .NET class in VB 6. When declaring the pmclass variable in Visual Basic, you need to declare it in terms of the interface, not the Performance Monitor class itself. When it comes time to create an instance of the class, you handle that task in the same way as before, by referencing the class.

Figure 10: In order for IntelliSense to work with .NET Classes in COM, you must declare your variables based on the interface and not the class itself.
Figure 10: In order for IntelliSense to work with .NET Classes in COM, you must declare your variables based on the interface and not the class itself.

There are more benefits realized from creating and implementing interfaces than just gaining IntelliSense functionality. An interface defines a contract and forces classes that implement the interface to conform to that contract.

Putting it all together

Figure 11 illustrates all four applications invoking the Performance Monitor class as well as the Windows Performance Monitor application itself. When an application increments, decrements or resets the performance counter, it is immediately reflected in every other application as well as the Performance Monitor application.

Figure 11: C#, VB .NET, VB 6, and VFP all using the Performance Monitor Class.
Figure 11: C#, VB .NET, VB 6, and VFP all using the Performance Monitor Class.

Summary

The goal of this article has been to show you how easy it is to employ COM components in .NET and .NET components in COM. If you have previously viewed your choices as .NET or COM, hopefully, after reading this article, you now realize that using both .NET and COM in the same solution is not only feasible, but may be the best course of action.