Chapter 25. Working with Classic COM and Interfaces

However much we try, we just cannot ignore the vast body of technology surrounding Microsoft's Component Object Model (COM). Over the years, this model has been the cornerstone of so much Microsoft-related development that we have to take a long, hard look at how we are going to integrate all that technology into the world of .NET.

This chapter begins by taking a brief backward glance at COM, and then compares it with the way that components interact in .NET. It also takes a look at the tools Microsoft provides to help link the two together. Having looked at the theory, you then try it out by building a few example applications. First you take a legacy basic COM object and run it from a Visual Basic 2008 program. Then you repeat the trick with a full-blown ActiveX control. Finally, you run some Visual Basic code in the guise of a COM object.

More information on how to make COM and VB6 code interoperate with the .NET platform can be found in Professional Visual Basic Interoperability: COM and VB6 to .NET (Wiley, 2002).

As you do all that, keep in mind one thing: COM is, to a large extent, where .NET came from. In addition, with all the time and resources that have been invested in this technology, it is important to consider the best ways to both maintain these investments and integrate them into new investments you make.

Understanding COM

Before looking into the COM-.NET interoperability story, it is important to understand COM's main concepts. This section does not attempt to do more than skim the surface, however. While the basic concepts are fundamentally simple, the underlying technology is anything but simple. Some of the most impenetrable books on software ever written have COM as their subject, and we have no wish to add to these.

COM was Microsoft's first full-blown attempt to create a language-independent standard for programming. The idea was that interfaces between components would be defined according to a binary standard. This means that you could, for the first time, invoke a VB component from a VC++ application, and vice versa. It would also be possible to invoke a component in another process or even on another machine, via Distributed COM (DCOM). You will not be looking at out-of-process servers here, however, because the vast majority of components developed to date are in process. Largely, DCOM was fatally compromised by bandwidth, deployment, and firewall problems, and never achieved a high level of acceptance.

A COM component implements one or more interfaces, some of which are standards provided by the system, and some of which are custom interfaces defined by the component developer. An interface defines the various members that an application may invoke. Once specified, an interface definition is supposed to be inviolate, so that even when the underlying code changes, applications that use the interface do not need to be rebuilt. If the component developers find that they have left something out, then they should define a new interface containing the extra functionality in addition to that in the original interface. This has, in fact, happened with a number of standard Microsoft interfaces. For example, the IClassFactory2 interface extends the IClassFactory interface by adding features for managing the creation of licensed objects.

The key to getting applications and components to work together is binding. COM offers two forms of binding, early and late:

  • In early binding, the application uses a type library at compile time to determine how to link in to the methods in the component's interfaces. A type library can exist as a separate file, with the extension .tlb, or as part of the DLL containing the component code.

  • In late binding, no connection is made between the application and its components at compile time. Instead, the COM runtime searches through the component for the location of the required member when the application is actually run. This has two main disadvantages: it is slower and unreliable. If a programming error is made (e.g., the wrong method is called, or the right method with the wrong number of arguments), then it is not caught at compile time.

When a type library is not explicitly referred to, there are two ways to identify a COM component: by class ID, which is actually a GUID, and by ProgID, which is a string and looks something like "MyProject.MyComponent". These are all cross-referenced in the registry. In fact, COM makes extensive use of the registry to maintain links between applications, their components, and their interfaces. All experienced COM programmers know their way around the registry blindfolded.

VB6 has a lot of COM features embedded into it, to the extent that many VB6 programmers are not even aware that they are developing COM components. For instance, if you create a DLL containing an instance of a VB6 class, then you have in fact created a COM object without even asking for one. The relative ease of this process is demonstrated in this chapter.

There are clearly similarities between COM and .NET, so to a large extent, all you have to do to make them work together is put a wrapper around a COM object to turn it into an assembly, and vice versa.

COM and .NET in Practice

It is time to get serious and see whether all this seamless integration really works. To do so, we have to simulate a legacy situation. Suppose your enterprise depends on a particular COM object that was written for you a long time ago by staff who are no longer in the organization. All you know about the component is that the code within it works perfectly and you need to employ it for your .NET application.

You have one, possibly two, options in this case. If you have the source code of the COM component (which is not always the case) and you have sufficient time (that is, money), then you can upgrade the object to .NET and continue to maintain it under Visual Studio 2008. For the purist, this is the ideal solution for going forward. However, maintaining the source as it exists under Visual Studio is not really a viable option. Visual Studio does offer an upgrade path, but it does not cope well with COM objects using interfaces specified as abstract classes.

If upgrading the object to a .NET component is not an option for you, then all you really can do is include the DLL as it stands as a COM object, register it on the server containing the .NET Framework, and use the .NET interoperability tools to integrate the two technologies. This is the path that this chapter takes for the example.

Therefore, what you need for this example is a genuine legacy COM object. This chapter uses a genuine legacy VB6 component to integrate within a .NET application. For the next section, this chapter steps back in time and uses VB6 for the classic component required. If you are not very interested in VB6, then feel free to skip this section. In any case, the DLL created is available as part of the code download from this book.

A Legacy Component

For the legacy component, imagine that you have some kind of analytics engine that requires a number of calculations. Because of the highly complex nature of these calculations, their development has been given to specialists, while the user interface for the application has been given to some UI specialists. A COM interface has been specified to which all calculations must conform. This interface has the name IMegaCalc and has the following methods:

Method

Description

Sub AddInput(InputValue as Double)

Adds the input value to the calculation

Sub DoCalculation( )

Performs the calculation

Function GetOutput( ) as Double

Gets the output from the calculation

Sub Reset( )

Resets the calculation for the next time

Step 1: Defining the Interface

When building any component, the first thing you have to do is define your interface. In VB6, the way to do this is to create an abstract class — that is, one without any implementation. Therefore, create an ActiveX DLL project called MegaCalculator. You do this by creating a new project and then changing its name to MegaCalculator by means of the Project

Step 1: Defining the Interface
Option Explicit

Public Sub AddInput(InputValue As Double)
End Sub
Public Sub DoCalculation()
End Sub

Public Function GetOutput() As Double
End Function

Public Sub Reset()
End Sub

From the main menu, select File

Step 1: Defining the Interface

Step 2: Implementing the Component

For the purposes of this demonstration, the actual calculation that you are going to perform is fairly mundane. In fact, the component will calculate the mean of a series of numbers. Create another ActiveX DLL project called MeanCalculator. Add a reference to the type library for the interface that you are going to implement by selecting the MegaCalculator DLL via the References dialog box that appears when you select Project

Step 2: Implementing the Component

Having done that, go ahead and write the code for the mean calculation. You do that in a class called MeanCalc:

Option Explicit

Implements IMegaCalc

Dim mintValue As Integer
Dim mdblValues() As Double
Dim mdblMean As Double

Private Sub Class_Initialize()
  IMegaCalc_Reset
End Sub

Private Sub IMegaCalc_AddInput(InputValue As Double)
  mintValue = mintValue + 1
  ReDim Preserve mdblValues(mintValue)
  mdblValues(mintValue) = InputValue
End Sub

Private Sub IMegaCalc_DoCalculation()
  Dim iValue As Integer
  mdblMean = 0#
  If (mintValue = 0) Then Exit Sub

  For iValue = 1 To mintValue
    mdblMean = mdblMean + mdblValues(iValue)
  Next iValue

  mdblMean = mdblMean / mintValue
End Sub

Private Function IMegaCalc_GetOutput() As Double
  IMegaCalc_GetOutput = mdblMean
End Function

Private Sub IMegaCalc_Reset()
  mintValue = 0
End Sub

As before, you select File

Step 2: Implementing the Component

Step 3: Registering the Legacy Component

If you have made it this far, then you should now have your legacy component. When developing your new .NET application on the same machine, you do not need to do anything more because your component is already registered by the build process. However, if you are working on an entirely new machine, then you must register it there. To do that, open a command window and register it with the following command using regsvr32.exe found at C:Windowssystem32:

regsvr32 MeanCalculator.dll

You should then see the result shown in Figure 25-1.

Figure 25-1

Figure 25.1. Figure 25-1

Because MeanCalculator implements an interface from MegaCalculator, you have to repeat the trick with that DLL:

regsvr32 MegaCalculator.dll

That action should yield the results shown in Figure 25-2. You are now ready to use your classic component from a .NET application.

Figure 25-2

Figure 25.2. Figure 25-2

The .NET Application

For the .NET application used in this chapter, you only need to instantiate an instance of the MeanCalc object and get it to figure out a mean calculation for you. In order to accomplish this task, create a .NET Windows Application project in Visual Basic called CalcApp. Laid out, the form looks like what is shown in Figure 25-3.

Figure 25-3

Figure 25.3. Figure 25-3

The two text boxes are called txtInput and txtOutput, respectively; the second one is not enabled for user input. The three command buttons are btnAdd, btnCalculate, and btnReset, respectively.

Referencing the Legacy COM Component from .NET

Before you dive into writing the code behind the buttons on the form, you first need to make your new application aware of the MeanCalculator component. Add a reference to the component via the Project

Referencing the Legacy COM Component from .NET
Figure 25-4

Figure 25.4. Figure 25-4

Press OK after you highlight both of the required components. Note that in the list of references in the Solution Explorer, you can now see the MeanCalculator and MegaCalculator components. If you don't see these items, be sure to press the Show All Files button in the Solution Explorer's toolbar. This view is presented in Figure 25-5.

Figure 25-5

Figure 25.5. Figure 25-5

Inside the .NET Application

Now that you have successfully referenced the components in the .NET application, you can finish coding the application, using the functionality provided via the COM components. To start making use of the new capabilities provided from the COM component, add to the code a global variable (mobjMean) that will hold a reference to an instance of the mean calculation component, as shown here:

Public Class Form1

  Dim mobjMean As MegaCalculator.IMegaCalc

Next, create a Form1_Load event to which you will add the following instruction, which creates the component you are going to use:

Private Sub Form1_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load

   mobjMean = New MeanCalculator.MeanCalc()

End Sub

Finally, add the code behind the form's buttons. First, working with the Add button, add the following code that calls the COM component:

Private Sub btnAdd_Click(ByVal sender As System.Object, _
                         ByVal e As System.EventArgs) _
Handles btnAdd.Click

   mobjMean.AddInput(CDbl(txtInput.Text))

End Sub

This adds whatever is in the input text box into the list of numbers for the calculation. Next, here's the code-behind for the Calculate button:

Private Sub btnCalculate_Click(ByVal sender As System.Object, _
                               ByVal e As System.EventArgs) _
                               Handles btnCalculate.Click

   mobjMean.DoCalculation()
   txtOutput.Text = mobjMean.GetOutput()

End Sub

This performs the calculation, retrieves the answer, and puts it into the output text box — all of this from the COM component. Finally, the code behind the Reset button simply resets the calculation:

Private Sub btnReset_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles btnReset.Click

   mobjMean.Reset()

End Sub

Trying It All Out

Of course, the proof of the pudding is in the eating, so let's see what happens when you run your application. Compile and run the application and place a value in the first text box — for example, 2 — and click the Add button on the form. Next, enter another value — for example, 3 — and click the Add button again. When you click Calculate, you'll get the mean of the two values (2.5 in this case), as shown in Figure 25-6.

Figure 25-6

Figure 25.6. Figure 25-6

Using TlbImp Directly

In the preceding example, there is actually quite a lot going on under the hood. Every time you import a COM DLL into Visual Studio, it creates a default interop assembly, which is basically a .NET assembly that acts as a wrapper for the COM object. If you are doing this a lot, then it might be better to do the wrapping once and for all, and then let your application developers import the resulting .NET assembly instead. Let's see how you might accomplish this task.

The process that creates the default interop assembly on behalf of Visual Studio is called TlbImp.exe. The name stands for Type Library Import, and that's pretty much what the process does. It is included in the .NET Framework SDK, and you might find it convenient to extend the PATH environment variable to include the in directory of the .NET Framework SDK.

TlbImp takes a COM DLL as its input and generates a .NET assembly DLL as its output. By default, the .NET assembly has the same name as the type library, which will — in the case of VB6 components — always be the same as the COM DLL. This means you have to explicitly specify a different output file. You do this by using the /out: switch. If you want to see what's going on at each step in the process, then you should also specify the /verbose flag:

tlbimp MegaCalculator.dll /out:MegaCalculatorNet.dll /verbose

For this example, start with MegaCalculator, because MeanCalculator has a reference to MegaCalculator. If you start with MeanCalculator, you get an error indicating that there is a reference to MegaCalculator and that TlbImp will not be able to overwrite the MegaCalculator.dll. The way to get around this is to start with MegaCalculator by giving TlbImp the command, as shown previously. Once this is accomplished, TlbImp will inform you of the success or failure in creating a .NET assembly of the name MegaCalculatorNet.dll.

Now that you have MegaCalculatorNet.dll in place, you can work with MeanCalculator and make sure that the reference now points to the new MegaCalculatorNet.dll. You can accomplish this by using the following command:

tlbimp MeanCalculator.dll /out:MeanCalculatorNet.dll
   reference:MegaCalculatorNet.dll /verbose

The result of this command is shown in Figure 25-7.

Figure 25-7

Figure 25.7. Figure 25-7

Notice that TlbImp has encountered a reference to another COM type library, MegaCalculator, and it has very kindly in turn imported MegaCalculatorNet instead. Having converted your COM DLLs into .NET assemblies, you can now reference them in an application as you would any other .NET DLL.

Late Binding

You've seen that you can successfully do early binding on COM components within a .NET application, but what if you want to do late binding instead? Suppose you don't have access to a type library at application development time. Can you still make use of the COM component? Does the .NET equivalent of late binding even exist?

The answer is yes, it does, but it is not as transparent as it is with VB6. Let's take a look at what occurred in VB6. If you wanted to do early binding, you would do this:

Dim myObj As MyObj
Set myObj = New MyObj

MyObj.MyMethod (...)

For late binding, it would look like this instead:

Dim myObj As Object
Set myObj = CreateObject ("MyLibrary.MyObject")
MyObj.MyMethod (...)

There is actually an enormous amount of activity going on under the hood here; and if you are interested in looking into this further, try Building N-Tier Applications with COM and Visual Basic 6.0 by Ash Rofail and Tony Martin (Wiley, 1999).

An Example for Late Binding

For the sample being built in this chapter, let's extend the calculator to a more generic framework that can feed inputs into a number of different calculation modules, rather than just the fixed one it currently implements. For this example, you'll keep a table in memory of calculation ProgIDs and present the user with a combo box to select the correct one.

The Sample COM Object

The first problem you encounter with late binding is that you can only late bind to the default interface, which in this case is MeanCalculator.MeanCalc, not MeanCalculator.IMegaCalc. Therefore, you need to redevelop your COM object as a standalone library, with no references to other interfaces.

As before, you'll build a DLL under the VB6 IDE, copy it over to your .NET environment, and reregister it there. Call this new VB6 DLL MeanCalculator2.dll; the code in the class (called MeanCalc) should look as follows:

Option Explicit

Dim mintValue As Integer
Dim mdblValues() As Double
Dim mdblMean As Double

Private Sub Class_Initialize()
  Reset
End Sub
Public Sub AddInput(InputValue As Double)
  mintValue = mintValue + 1
  ReDim Preserve mdblValues(mintValue)
  mdblValues(mintValue) = InputValue
End Sub

Public Sub DoCalculation()
  Dim iValue As Integer
  mdblMean = 0#

  If (mintValue = 0) Then Exit Sub

  For iValue = 1 To mintVal
    mdblMean = mdblMean + mdblValues(iValue)
  Next iValue

  mdblMean = mdblMean / mintValue
End Sub

Public Function GetOutput() As Double
  GetOutput = mdblMean
End Function

Public Sub Reset()
  mintValue = 0
End Sub

As before, move this across to your .NET server and register it using RegSvr32.

The Calculation Framework

For your generic calculation framework, you'll create a new application in Visual Basic 2008 called CalcFrame. You will basically use the same dialog box as before, but with an extra combo box at the top of the form. This new layout is illustrated in Figure 25-8.

Figure 25-8

Figure 25.8. Figure 25-8

The new combo box is called cmbCalculation. For this to work, you also need to disable the controls txtInput, btnAdd, btnCalculate, and btnReset until you know whether the selected calculation is valid. Begin your application by importing the Reflection namespace, which you need for handling the application's late binding:

Imports System.Reflection

Once the form is in place, add a few member variables to the code of your application:

Public Class Form1
    Inherits System.Windows.Forms.Form
    Private mstrObjects() As String
    Private mnObject As Integer
    Private mtypCalc As Type
    Private mobjcalc As Object

From there, add a few new lines to Form1_Load:

Private Sub Form1_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load

    mnObject = 0
    AddObject("Mean", "MeanCalculator2.MeanCalc")
    AddObject("StdDev", "StddevCalculator.StddevCalc")

    If (mnObject > 0) Then
        cmbCalculation.SelectedIndex = 0
    End If
End Sub

What you are doing here is building a list of calculations. When you're finished, you select the first one in the list. Let's take a look at that subroutine AddObject:

Private Sub AddObject(ByVal strName As String, ByVal strObject As String)
    cmbCalculation.Items.Add(strName)
    mnObject = mnObject + 1
    ReDim Preserve mstrObjects(mnObject)
    mstrObjects(mnObject - 1) = strObject
End Sub

The preceding code segment adds the calculation name to the combo box, and its ProgID to an array of strings. Neither of these is sorted, so you get a one-to-one mapping between them. Check out what happens when you select a calculation via the combo box:

Private Sub cmbCalculation_SelectedIndexChanged(ByVal sender As System.Object, _
                                           ByVal e As System.EventArgs) _
                           Handles cmbCalculation.SelectedIndexChanged
    Dim intIndex As Integer
    Dim bEnabled As Boolean

    intIndex = cmbCalculation.SelectedIndex
    mtypCalc = Type.GetTypeFromProgID(mstrObjects(intIndex))

    If (mtypCalc Is Nothing) Then
        mobjcalc = Nothing
bEnabled = False
    Else
        mobjcalc = Activator.CreateInstance(mtypCalc)
        bEnabled = True
    End If

    txtInput.Enabled = bEnabled
    btnAdd.Enabled = bEnabled
    btnCalculate.Enabled = bEnabled
    btnReset.Enabled = bEnabled
End Sub

There are two key calls in this example. The first is to Type.GetTypeFromProgID. This takes the incoming ProgID string and converts it to a Type object. This process either succeeds or fails; if it fails, then you disable all the controls and let the user try again. If it succeeds, however, then you create an instance of the object described by the type. You do this in the call to the static method Activator.CreateInstance().

For this example, assume that the user has selected a calculation that you can successfully instantiate. What next? The user enters a number and clicks the Add button on the form:

Private Sub btnAdd_Click(ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles btnAdd.Click

        Dim objArgs() As [Object] = {CDbl(txtInput.Text)}
        mtypCalc.InvokeMember("AddInput", BindingFlags.InvokeMethod, _
           Nothing, mobjcalc, objArgs)

    End Sub

The important call here is to the InvokeMember() method. Let's take a closer look at what is going on. Five parameters are passed into the InvokeMember() method:

  • The first parameter is the name of the method that you want to call: AddInput in this case. Therefore, instead of going directly to the location of the routine in memory, you ask the .NET runtime to find it for you.

  • The value from the BindingFlags enumeration tells it to invoke a method.

  • The next parameter provides language-specific binding information, which is not needed in this case.

  • The fourth parameter is a reference to the COM object itself (the one you instantiated using Activator.CreateInstance).

  • Finally, the fifth parameter is an array of objects representing the arguments for the method. In this case, there is only one argument, the input value.

Something very similar to this is going on underneath VB6 late binding, except that here it is exposed in all its horror. In some ways, that's not a bad thing, because it should highlight the point that late binding is something to avoid if possible. Anyway, let's carry on and complete the program. Here are the remaining event handlers for the other buttons:

Private Sub btnCalculate_Click(ByVal sender As System.Object, _
               ByVal e As System.EventArgs) Handles btnCalculate.Click
Dim objResult As Object
       mtypCalc.InvokeMember("DoCalculation", BindingFlags.InvokeMethod, _
                            Nothing, mobjcalc, Nothing)
       objResult = mtypCalc.InvokeMember("GetOutput", _
                    BindingFlags.InvokeMethod, Nothing, mobjcalc, Nothing)
       txtOutput.Text = objResult

  End Sub

  Private Sub btnReset_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles btnReset.Click

      mtypCalc.InvokeMember("Reset", BindingFlags.InvokeMethod, _
         Nothing, mobjcalc, Nothing)

  End Sub

Running the Calculation Framework

Let's quickly complete the job by running the application. Figure 25-9 shows what happens when you select the nonexistent calculation StdDev.

Figure 25-9

Figure 25.9. Figure 25-9

As shown in the screen shot, the input fields have been disabled, as desired. Figure 25-10 shows what happens when you repeat the earlier calculation using Mean. This time, the input fields are enabled, and the calculation can be carried out as before.

Figure 25-10

Figure 25.10. Figure 25-10

One final word about late binding. You took care to ensure that you checked whether the object was successfully instantiated. In a real-life application, you also need to ensure that the method invocations are successful and that all exceptions are caught — you do not have the luxury of having the compiler find all your bugs for you.

ActiveX Controls

Let's move on from basic COM objects to ActiveX controls. You are going to do pretty much the same thing you did with the basic COM component (apart from late binding, which has no relevance to ActiveX controls): build a legacy control using VB6 and then import it into your .NET Visual Basic project.

The Legacy ActiveX Control

For your legacy ActiveX control, you are going to build a simple button-like object that is capable of interpreting a mouse click and can be one of two colors according to its state. To accomplish this task, you will take a second foray into VB6, so if you don't have VB6 handy, feel free to skip the next section, download the OCX file, and pick it up when you start developing your .NET application.

Step 1: Creating the Control

This time, within the VB6 IDE, you need to create an ActiveX Control project. For this example, call the project Magic, and the control class MagicButton, to reflect its remarkable powers. From the Toolbox, select a Shape control and place it on the UserControl form that VB6 provides for you. Rename the shape provided on the form to shpButton, and change its properties as follows:

Property

Value

FillStyle

0Solid

Shape

4Rounded Rectangle

FillColor

Gray (&H00808080&)

Add a label on top of the Shape control and rename it to lblText. Change this control's properties to the following:

Property

Value

BackStyle

0 — Solid 0Transparent

Alignment

2Center

Switch to the code view of the MagicButton component. Within the code presented, add two properties called Caption and State, and an event called Click(), as well as code to handle the initialization of the properties and persisting them, to ensure that the shape resizes correctly and that the label is centered.

You also need to handle mouse clicks within the code. The final code of the MagicButton class should look as follows:

Option Explicit

Public Event Click()

Dim mintState As Integer

Public Property Get Caption() As String
  Caption = lblText.Caption
End Property

Public Property Let Caption(ByVal vNewValue As String)
  lblText.Caption = vNewValue
  PropertyChanged ("Caption")
End Property

Public Property Get State() As Integer
  State = mintState
End Property

Public Property Let State(ByVal vNewValue As Integer)
  mintState = vNewValue
  PropertyChanged ("State")

  If (State = 0) Then
    shpButton.FillColor = &HFFFFFF&
  Else
    shpButton.FillColor = &H808080&
  End If
End Property

Private Sub UserControl_InitProperties()
  Caption = Extender.Name
  State = 1
End Sub

Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
  Caption = PropBag.ReadProperty("Caption", Extender.Name)
  State = PropBag.ReadProperty("State", 1)
End Sub

Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
  PropBag.WriteProperty "Caption", lblText.Caption
  PropBag.WriteProperty "State", mintState
End Sub

Private Sub UserControl_Resize()
  shpButton.Move 0, 0, ScaleWidth, ScaleHeight
  lblText.Move 0, (ScaleHeight - lblText.Height) / 2, ScaleWidth
End Sub

Private Sub lblText_Click()
RaiseEvent Click
End Sub

Private Sub UserControl_MouseUp(Button As Integer, Shift As Integer, _
                                X As Single, Y As Single)
  RaiseEvent Click
End Sub

If you build this, you'll get an ActiveX control called Magic.ocx.

Step 2: Registering Your Legacy Control

You now have your legacy control. As before, if you are developing your new .NET application on the same machine, then you don't need to do anything more, because your control will already be registered by the build process. However, if you are working on an entirely new machine, then you need to register it there. As before, open a command window and register it as follows:

regsvr32 Magic.ocx

Having done that, you are ready to build your .NET application.

A .NET Application, Again

This .NET application is even more straightforward than the last one. All you are going to do this time is display a button that changes color whenever the user clicks it. To begin, create a .NET Windows Application project in Visual Basic called "ButtonApp." Before you start to develop it, however, extend the Toolbox to incorporate your new control by selecting Tools

A .NET Application, Again
Figure 25-11

Figure 25.11. Figure 25-11

When you click the OK button, your MagicButton class is now available to you in the Toolbox (see Figure 25-12). Add the Magic.MagicButton control to your form, as shown in Figure 25-13, by checking the box next to the control name. Note that references to AxMagic and Magic are added to the project in the Solution Explorer window within the References folder, as shown in Figure 25-14.

Figure 25-12

Figure 25.12. Figure 25-12

Figure 25-13

Figure 25.13. Figure 25-13

All you need to do now is initialize the Caption property to ON, change the Text of the form to Button Application, and code up a handler for the mouse Click event:

Private Sub AxMagicButton1_ClickEvent(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles AxMagicButton1.ClickEvent

    AxMagicButton1.CtlState = CType(1 - AxMagicButton1.CtlState, Short)
    If (AxMagicButton1.CtlState = 0) Then
        AxMagicButton1.Caption = "OFF"
    Else
AxMagicButton1.Caption = "ON"
    End If

End Sub
Figure 25-14

Figure 25.14. Figure 25-14

Note something slightly peculiar happening here. In the course of importing the control into .NET, the variable State mutated into CtlState. This is because there is already a class in the AxHost namespace called State, which is used to encapsulate the persisted state of an ActiveX control.

Trying It All Out, Again

When you run this application, note the control in the ON position, as shown in Figure 25-15. If you click the control, it changes to the OFF position, as shown in Figure 25-16.

Figure 25-15

Figure 25.15. Figure 25-15

Figure 25-16

Figure 25.16. Figure 25-16

Using .NET Components in the COM World

So far, this chapter has established, through a couple of examples, that you can use your COM legacy components within any of your .NET-based applications. You do not have to throw everything out quite yet. Now it's time to consider the opposite question: Can you run .NET components in the COM world?

Why on earth would you want to run .NET components in the COM world? It is not immediately obvious, in fact, because migration to .NET would almost certainly be application-led in most cases, rather than component-led. However, it is possible (just) to imagine a situation in which a particularly large application remains not based on .NET, while component development moves over to .NET. Let's assume that's the case for the next section. The technology is quite cool, anyway.

A .NET Component

Let's take a look at the .NET component. Here, you will implement an exact copy of the functionality created earlier with the MegaCalculator and MeanCalculator components, except you will use Visual Basic, rather than VB6.

Begin by creating a Class Library project called MegaCalculator2. Here is the entire code of the interface for the class library:

Public Interface IMegaCalc

  Sub AddInput(ByVal InputValue As Double)

  Sub DoCalculation()
  Function GetResult() As Double
  Sub Reset()

End Interface

Now create another Class Library project called MeanCalculator3. This will contain a class called MeanCalc that is going to implement the IMegaCalc interface, in a precise analog of the MeanCalc in your original VB6 MeanCalculator project. As before, you need to add a reference to MegaCalculator2 first, although this time it will be a true .NET Framework reference, and you'll have to browse for it (see Figure 25-17).

Figure 25-17

Figure 25.17. Figure 25-17

Here is the code:

Public Class MeanCalc
  Implements MegaCalculator2.IMegaCalc

  Dim mintValue As Integer
  Dim mdblValues() As Double
  Dim mdblMean As Double

  Public Sub AddInput(ByVal InputValue As Double) _
      Implements MegaCalculator2.IMegaCalc.AddInput
    mintValue = mintValue + 1
    ReDim Preserve mdblValues(mintValue)
    mdblValues(mintValue - 1) = InputValue
  End Sub

  Public Sub DoCalculation() _
      Implements MegaCalculator2.IMegaCalc.DoCalculation
    Dim iValue As Integer

    mdblMean = 0

    If (mintValue = 0) Then Exit Sub

    For iValue = 0 To mintValue - 1 Step 1
      mdblMean = mdblMean + mdblValues(iValue)
    Next iValue

    mdblMean = mdblMean / iValue
  End Sub
Public Function GetResult() As Double Implements _
                  MegaCalculator2.IMegaCalc.GetResult
    GetResult = mdblMean
  End Function

  Public Sub Reset() Implements MegaCalculator2.IMegaCalc.Reset
    mintValue = 0
  End Sub

  Public Sub New()
    Reset()
  End Sub

End Class

Before compiling this application, make the component that you are building COM-visible. To do this, right-click on the MeanCalculator3 solution within Visual Studio 2008 and select Properties from the provided menu.

From the Properties dialog, select the Compile tab, where you will find a check box called Register for COM Interop (see Figure 25-18). Make sure that this is checked and then compile the application.

Figure 25-18

Figure 25.18. Figure 25-18

This component is quite similar to the VB6 version, apart from the way in which Implements is used. After this is all in place, build the assembly. If you have security issues with this compilation, then you need to ensure that you are running Visual Studio as an Administrator. Now we come to the interesting part: How do you register the resulting assembly so that a COM-enabled application can make use of it?

RegAsm

The tool provided with the .NET Framework SDK to register assemblies for use by COM is called RegAsm. This tool is very simple to use. If all you are interested in is late binding, then you simply run it as presented in Figure 25-19.

Figure 25-19

Figure 25.19. Figure 25-19

The only challenge with RegAsm is finding the thing. It is usually found lurking in C:WindowsMicrosoft.NETFramework2.0.50727, even if you are working with the .NET Framework 3.0 or 3.5. You might find it useful to add this to your path in the system environment. You can also use the Visual Studio command prompt to directly access this tool.

However, there is probably even less reason for late binding to an exported .NET component than there is for early binding, so we'll move on to look at early binding. For this, you need a type library, so add another parameter, /tlb (see Figure 25-20).

Figure 25-20

Figure 25.20. Figure 25-20

Now when you look in the target directory, not only do you have the original MeanCalculator3.dll, but you've also acquired a copy of the MegaCalculator2.dll and two type libraries: MeanCalculator3.tlb and MegaCalculator2.tlb. You need both of these, so it was good of RegAsm to provide them for you. You need the MegaCalculator2 type library for the same reason that .NET needed the MegaCalculator assembly: because it contains the definition of the IMegaCalc interface that MeanCalculator is using.

Testing with a VB6 Application

Turning the tables again, build a VB6 application to see whether this is really going to work. Copy the type libraries over to your pre-.NET machine (if that is where VB6 is running) and create a Standard EXE project in VB6. Call this project "CalcApp2." Within this project, you need to create references to the two new type libraries, so go to the References dialog box, browse to find them, and select them, as shown in Figure 25-21.

Figure 25-21

Figure 25.21. Figure 25-21

At this point, you have everything you need to create the application. Create it as you did for the Visual Basic CalcApp (see Figure 25-22). As before, the text boxes are txtInput and txtOutput, respectively, and the command buttons are btnAdd, btnCalculate, and btnReset.

Figure 25-22

Figure 25.22. Figure 25-22

Here's the code behind it:

Option Explicit

Dim mobjCalc As MeanCalculator3.MeanCalc
Dim mobjMega As MegaCalculator2.IMegaCalc

Private Sub btnAdd_Click()
  mobjMega.AddInput (txtInput.Text)
End Sub

Private Sub btnCalculate_Click()
  mobjMega.DoCalculation
  txtOutput.Text = mobjMega.GetResult
End Sub

Private Sub btnReset_Click()
  mobjMega.Reset
End Sub

Private Sub Form_Load()
  Set mobjCalc = New MeanCalculator3.MeanCalc
  Set mobjMega = mobjCalc
End Sub

Notice that this time you have to explicitly get a reference to the interface IMegaCalc. The default interface of the component, MeanCalc, is entirely empty.

Make the executable via the File

Figure 25-22
Figure 25-23

Figure 25.23. Figure 25-23

Well, that's not what you expected. What's happened here? In COM, the location of the DLL containing the component is available via the registry. In .NET, the assembly always has to be in either the current directory or the global assembly. All the registry is doing for you here is converting a COM reference to a .NET one; it is not finding the .NET one for you.

Fortunately, this is easy to sort out. To resolve the problem, move the two assemblies, MegaCalculator3 and MeanCalculator2, to your current directory and try again (see Figure 25-24).

Figure 25-24

Figure 25.24. Figure 25-24

That's better. You've established that in the unlikely event of having to run .NET from a COM-oriented application, Microsoft has provided you with the tools necessary to do the job.

TlbExp

In fact, Microsoft provides you with not one, but two alternative tools. The other one is TlbExp, which, as its name suggests, is the counterpart of TlbImp. You can use TlbExp to achieve the same result as RegAsm in the previous section.

Summary

COM is not going to go away for quite some time, so .NET applications have to interoperate with COM, and they have to do it well. By the end of this chapter, you have achieved several things:

  • You made a .NET application early bind to a COM component, using the import features available in Visual Basic.

  • You looked at the underlying tool, Tlbimp.

  • You managed to make the application late bind as well, although it wasn't a pleasant experience.

  • You incorporated an ActiveX control into a .NET user interface, again using the features of Visual Basic.

  • You looked at using Regasm and TlbExp to export type libraries from .NET assemblies, in order to enable VB6 applications to use .NET assemblies as if they were COM components.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset