The second example application (InteropDemo.sln with InteropDebug.vbp) consists of a VB 6.0 Windows Form project calling a VB .NET component to add two numbers together. This time I'm back to using an artificial component rather than a real-life one. The idea is that you can readily use and debug your .NET components from your VB 6.0 applications.
Figure 11-5 shows the very simple user interface for this application. The single button shown on the VB 6.0 Windows Form calls a public method, AddTwoNumbers , in the VB .NET DLL. This method takes two numbers and simply adds them together.
Listing 11-1 shows the VB .NET class that forms the component that will be called by the VB 6.0 application.
Option Strict On Imports System.Runtime.InteropServices <ClassInterface(ClassInterfaceType.AutoDual)> _ Public Class MathClass Public Sub New() End Sub Public Function AddTwoNumbers(ByVal FirstNumber As Double, _ ByVal SecondNumber As Double) As Double 'Return the result to COM client Return FirstNumber + SecondNumber End Function End Class
First you need to build the VB .NET component, which is the InteropDemo class library project in InteropDemo.sln. To allow a COM application to use this component, go to the project's Properties ’ Configuration Properties ’ Build property page and select the Register for COM Interop check box.
When you mark the project for COM Interop in this fashion, Visual Studio creates a .NET Interop assembly called Interop.InteropDemo.dll . The CLR will make this component accessible to any COM application by creating a COM Callable Wrapper (CCW). The CCW operates just like the RCW that I discussed previously, except in the opposite direction. It fools the COM application into thinking that it's calling a COM component rather than a .NET component.
As with the previous application, you need to switch on unmanaged debugging for the VB .NET project by right-clicking the project in the Solution Explorer window and going to the Configuration Properties ’ Debugging property page. There you should select the check box marked Unmanaged code debugging. If you forget to do this, your VB 6.0 breakpoints won't be recognized.
Compiling the InteropDemo solution automatically registers it as far as COM is concerned , thus enabling VB 6.0 to see the component and its public interfaces. If you have a .NET assembly that isn't registered on the machine on which you wish to use it, you can register it using regasm.exe from the command line. You can consider regasm.exe as the .NET equivalent of regsvr32.exe for COM registration.
Now you can prepare the InteropDebug VB 6.0 application for debugging. Once you've loaded the InteropDebug.vbp project, you need to add a reference to the .NET component that you've just built. To do this, go to the Project ’ References menu option and select InteropDemo from the list of available references.
After you add the .NET assembly reference, save the VB 6.0 application and then compile it by going to the File ’ Make InteropDebug.exe menu option. Click the Options button on the Make Project dialog window, and go to the Compile tab, as shown earlier in Figure 11-2. You must choose the Compile to Native Code option because the Visual Studio debugger doesn't understand p-code . You should also select the No Optimization and Create Symbolic Debug Info compilation options. The former option allows the debugger to map the executing program back to the source code accurately, just as the same option works when compiling a VB .NET application. The latter option ensures that the VB 6.0 compiler produces a debug symbol file, without which the binary-to-source code mapping can't occur.
Because you're debugging this application completely within Visual Studio, you need a way of starting the VB 6.0 part of the application from Visual Studio. To do this, use Solution Explorer to go to the Configuration Properties ’ Debugging property page of the InteropDemo project and change the Start Action to be Start external program. Then navigate to the InteropDebug.exe VB 6.0 program, as this will launch the whole application. On the same property page, ensure that unmanaged debugging is switched on.
Next, click the InteropDemo solution in Solution Explorer and go to the File ’ Add Existing Item menu option. Navigate to the folder containing the VB 6.0 application and select FormInterop.frm. This is the VB 6.0 user interface form that you're going to step through in debug mode. You should see in Solution Explorer that this form has been added to the solution in its own category titled "Solution Items." If you now double-click this form in Solution Explorer, a Source window opens showing the VB 6.0 code that makes up the form. Add a breakpoint to line 28 (the start of the DebugTest_Click method) in the FormInterop.frm source window, and finally press F5 to start the application.
When you click the button on the VB 6.0 form, the debugger will break into the VB 6.0 source code at line 28, where you placed your breakpoint. Now single-step through the code. On line 32, the debugger will jump from the unmanaged VB 6.0 world to the managed VB .NET world, to execute the VB .NET class constructor. When you single-step to the end of the constructor, you can see something interesting. The debugger refuses to step back to the unmanaged world, unless you place another breakpoint in the VB 6.0 code. So the debugger doesn't reach line 32 in the VB 6.0 code, but instead stops single-stepping completely and runs the program until the result of the method call is displayed on the command button. At the time of this writing, this is a known COM Interop debugging problem with stepping into VB .NET class constructors, although it may have been fixed by the time that you read this. The only workaround I've found is to add breakpoints in the VB 6.0 and/or VB .NET code at the places where I want the debugger to stop the program flow. You should be aware that a .NET class must have a public default constructor if you want to use it from VB 6.0, but you shouldn't step into this constructor while debugging.
Now that I've shown you this issue, you should remove the breakpoint on line 28 of the VB 6.0 code and instead add a new breakpoint at line 32. By not telling the debugger to step into the VB .NET class constructor, you should find that stepping between managed and unmanaged code is much more stable. Press F5 to start the application again and click the button. The debugger now stops on line 32 and you can step into the VB .NET method and then back into the VB 6.0 code after the method call has completed. If you continue to single-step after End Sub on line 36 of the VB 6.0 code, you may find that the debugger steps into assembly code in the Disassembly window. This is because there are no more lines of source code for the debugger to step through, so it presents the VB 6.0 assembly code instead. Just press F5 to tell the debugger to stop single-stepping and the result of the method call is displayed on the command button.
As you can see from the two examples just presented, it's very easy to debug your COM Interop projects just like your pure VB .NET projects. As long as you keep in mind the minor caveats that I've mentioned, you can almost ignore the type of code that's being debugged . The rest of this chapter covers situations where COM Interop problems are somewhat more complex.