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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.