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.
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.
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.
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 |
---|---|
| Adds the input value to the calculation |
| Performs the calculation |
| Gets the output from the calculation |
| Resets the calculation for the next time |
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
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
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
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
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.
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.
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.
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.
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
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.
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
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.
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.
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.
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).
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 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
.
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.
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
Let's quickly complete the job by running the application. Figure 25-9 shows what happens when you select the nonexistent calculation StdDev
.
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.
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.
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.
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.
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:
Add a label on top of the Shape
control and rename it to lblText
. Change this control's properties to the following:
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
.
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.
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
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.
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
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.
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.
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.
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).
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.
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?
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.
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).
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.
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.
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
.
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
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).
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.
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.