HomeDigital EditionSearch Dotnet Cd
ASP.NET C# Certification Exams The CLI Data Access Editorials Extending .NET Fundamentals Interoperability Interviews Migrate Mobile .NET Mono .NET Interface Object-Oriented Programming Open Source Optimization Product/Book Reviews Security Source Code UML Visual Studio .NET

The .NET Framework is stuffed full of treats for the VB developer. Long-awaited improvements such as strict type checking, structured exception handling, and true object-oriented support extend the power of the language dramatically - but at a price. The only way a Visual Basic 6 program can be brought to the .NET world is through a painful migration process that rarely runs without incident. In this article, we'll consider some of these ugly problems - and the insights you can gain from them.

Interop Versus Migration
You may have already read about RCW (Runtime Callable Wrappers), which allow .NET programs to easily, elegantly communicate with legacy COM components. .NET really shines in all aspects of COM interoperability. However, there's been much less written about a far thornier problem: migrating legacy VB code into a VB.NET project.

For VB programmers, this migration process is handled by a special Upgrade Wizard provided in the Visual Studio .NET IDE. When you attempt to open a VB 6 project file in VS.NET, the Upgrade Wizard is launched automatically (see Figure 1). It then scans through your project line by line, attempting to modify syntax and make the required substitutions by relying on an internal VB 6 to VB.NET compatibility list. For a large or complex project, the process is surprisingly slow.

Figure 1

All in all, the Upgrade Wizard does remarkably well for simple applications. This feat is particularly amazing when you consider the migration hurdles it must overcome:

  • There is no file compatibility between Visual Basic .NET and earlier versions. VB.NET files have different extensions (.vb instead of .mod or .frm, and .vbproj instead of .vbp), and use a block structure that allows modules, forms, and classes to be combined in a single file.
  • The syntax of the VB language has been updated in .NET. The alterations range from minor cosmetic changes (like mandatory function brackets) to entirely new concepts such (like namespaces and inheritance).
  • VB.NET requires a managed RCW class to wrap every ActiveX control or COM component it needs to use. Thankfully, the Upgrade Wizard creates these assemblies automatically, and modifies the imported code to use them.
  • VB 6 uses a special syntax for forms (which you've seen if you've ever opened a .frm file in Notepad). VB.NET, on the other hand, creates forms out of ordinary classes, and adds a series of VB code statements to implement your design-time tweaking.
  • VB 6 uses the proprietary Ruby engine for displaying forms. VB.NET uses the common controls from the .NET class library, which expose a different set of properties and methods and use an entirely new event model.

    Unfortunately, these problems are difficult to escape. If you try to migrate a traditional, modestly complex VB 6 application, you'll find that the Upgrade Wizard often falls flat on its face.

    A Sample Migration Disaster
    I discovered a whole new level of .NET incompatibilities when I migrated a midsize VB 6 ordering application to the .NET platform. This program (which I'll call VB6OrderMaker) allows users to order specialty products and submit an invoice via e-mail. It consists of about three dozen forms, most of which are used to provide different product listings. A few other capabilities, like multiple price sets and dynamic print previews, are also part of the mix. As a whole, this program is a good example of typical, slightly old-fashioned VB 6 coding: it doesn't make much use of classes or interfaces, but the code is logical and well organized.

    The Upgrade Wizard went to work for over an hour on the VB6OrderMaker application, and ended with an Upgrade Report presenting 360 errors; 2,889 warnings; and 3,249 total issues (see Figure 2), making for an average of about 80 issues per file. The only file that made the jump to .NET without any problems was the module used to run functions from the Windows API.

    Figure 2

    So what derailed the VB6OrderMaker migration? The list of problems took a long time to sort through, but eventually several distinct themes emerged. These themes represent some of the issues I've seen recur time and time again with other VB 6 migration projects.

    The Demise of Proprietary Arrays
    In VB.NET, every array must use 0 as a lower bound. This change was introduced to make VB more compatible with other programming languages (like C#), ensuring that all types of .NET developers can easily share custom code snippets and insight. Unfortunately, this change also spells significant trouble for VB developers who have been using their own proprietary arrays.

    In the VB6OrderMaker project, array indexes are chosen with specific meanings. For example, the elements 1 and greater are used to contain the items in an order. Element 0 contains a combined line with price totals, and negative array indexes are used to contain licensing information for the ordered items. Even though these specific array indexes are stored in variables instead of being hard-coded, there's no easy way to update the code to meet the new 0-bound requirement. And without fixing this problem, the VB6OrderMaker program can't even be compiled.

    The VB 6 Solution
    Unlike some of the other migration headaches, this disaster could have been avoided. A better approach is to not use arrays at all in VB 6, but to move straight to collections or a custom class. In the VB6OrderMaker application, orders could be stored in an Order class that contains a collection of ordered items and other license-specific properties. This class could also contain built-in methods for retrieving the total cost of all ordered items, and for converting prices to different currencies. The Order class approach would improve encapsulation, and would result in a data structure that doesn't depend on miscellaneous conversion and helper functions in other modules of the program.

    The Secret Variant
    Variants are a special VB 6 data type that can store different types of information, including strings or numbers. A variant converts itself automatically according to how you try to use it. Historically, this automatic conversion led to a variety of annoyances, although it was useful in some situations.

    The problem is that even though variants are rarely used deliberately in VB 6 code, they are accidentally created every time a programmer forgets to specify a data type.

    VB 6 code
    ' intB will be an Integer, but intA will be a _variant.
    Dim intA, intB As Integer

    This type of mistake is generally harmless. However, in does result in additional migration warnings. Because VB.NET does not support variants, the Upgrade Wizard automatically converts all variants into the generic System.Object type. Thus, after a typical migration, you may find yourself with many more generic Objects than you expected.

    VB.NET migrated code
    Dim intA As Integer, intB As Object

    If you are using loose type checking (Option Strict Off), the generic Object can still be converted into a simple data type (like a string or integer) as needed. But because the compiler can't verify these operations, each one will be flagged with a warning:

    Dim intA As Integer, intB As Object
    ' UPGRADE_WARNING: Couldn't resolve default _ property of object intB.
    ' Click for more: ms-help://MS.MSDNVS/vbcon/html/vbup1037.htm
    intB = 0

    The VB 6 Solution:
    This minor issue isn't a problem; in fact, this portion of the code will still work perfectly. However, this idiosyncrasy leads to a large number of additional warnings in many migrated projects. To eliminate it entirely, double-check all your variables for missing data types. Unfortunately, there's no tool that can help automate this tedious process.

    The Control Array Kludge
    Control arrays present a particularly unpleasant example of what can go wrong with migration. In VB 6, control arrays are often an excellent way to solve certain problems. In VB6OrderMaker, control arrays allow the program to loop through a series of controls and update them based on array information. For example, you can copy pricing information into a series of text boxes using this syntax:

    VB 6 code:
    For i = 0 to UBound(PriceArray)
    txtPrice(i).Text = Val(PriceArray(i)
    Next i

    Control arrays also allow a VB 6 program to use a single event handler for numerous similar controls, which is an extremely useful convenience. For example, you could implement a set of dynamically highlighting labels, like this:

    VB 6 code
    Private Sub Description_MouseMove
    (Index As Integer, _
    Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
    Description(Index).ForeColor _
    = &H800000
    End Sub

    In .NET, control arrays aren't needed to handle multiple events. Instead, you can use the Handles clause or the AddHandler statement to link up as many controls as you want to a single event handler. You can then use the sender parameter to interact with the control that fires the event.

    VB.NET code:
    Private Sub Highlight(sender As _ Object, e As MouseEventArgs) _
    Handles lblLine1.MouseMove, _ lblLine2.MouseMove
    Dim lbl As Label = _ CType(sender, Label)
    lbl.ForeColor = _ Color.RoyalBlue
    End Sub

    The VB 6 Solution
    There's no easy fix for this problem. Control arrays are a useful tool in VB 6, but they have been replaced with a more modern system in VB.NET. However, if you import a program such as VB6OrderMaker that uses control arrays, the Upgrade Wizard doesn't convert them. Instead, it uses a special compatibility class, depending on your control. For example, if you have a control array of label controls, you'll end up using Microsoft.VisualBasic.Compatibility.VB6.LabelArray. This control allows VB.NET to "fake" a control array, with predictably inelegant results. This is an example of the Upgrade Wizard at its worst: importing legacy problems from VB 6 into the .NET world.

    Pointers, Handles, and Other Low-Level Tricks
    Visual Basic provides several undocumented functions that allow expert VB coders to retrieve the memory addresses where simple variables or objects are stored, including VarPtr, StrPtr, and ObjPtr. After retrieving this information, you could pass it to a DLL routine or to the Windows API, which sometimes required this information. In VB.NET, you will rarely need to have this sort of low-level access to memory information, which complicates your code and can introduce obscure bugs. If, however, you need to interact with a DLL or code component that needs an address, you can still retrieve it - but you need to "pin down" the memory first. Pinning down the memory ensures that the Common Language Runtime won't try to move a value between the time when you find its address and the time when the DLL tries to use it. (This automatic movement feature is one of the ways by which the .NET runtime attempts to improve performance.)

    The following example creates and pins down a handle for an object called MyObj. It uses a special type called GCHandle in the System.Runtime.InteropServices namespace:

    VB .NET code:
    Dim MyGCHandle As GCHandle = _
    GCHandle.Alloc(MyObj, GCHandle _
    Type.Pinned)
    Dim Address As IntPtr = _
    MyGCHandle.AddrOfPinnedObject()
    ' (Invoke the DLL or do something _
    with the Address variable here.)

    ' Allow the object to be moved _ again.
    MyGCHandle.Free()

    The VB 6 Solution
    The code you use is up to you, but be aware that the Upgrade Wizard will simply flag attempts to use the VarPtr, StrPtr, or ObjPtr function with an error. You'll have to add the code above on your own.

    A New Menu Model
    Menus represent one more case in which a minor change in the programming model spells a major migration headache. In VB 6, you have to create a pull-down menu before you can use it in a context menu. You then rely on the built-in PopupMenu command:

    VB 6 code:
    Private Sub Form_MouseDown(Button _
    As Integer, Shift As Integer, _
    X As Single, Y As Single)
    If Button = 2 Then
    PopupMenu mnuOrderOptions
    End If
    End Sub

    In VB.NET, you can't use the same menu for a main menu and a context menu. Instead, you'll need to create a new ContextMenu object, and copy the ordinary menu information into it with the CloneMenu() method:

    VB .NET code:
    Dim mnuPopUp As New ContextMenu()
    Dim mnuItem As MenuItem

    For Each mnuItem In mnuOrder _
    Options.MenuItems
    ' The CloneMenu method _
    ensures that the context menu _
    and main menu items have the same _ event handlers.
    mnuPopUp.MenuItems.Add(mnuItem. _
    CloneMenu())
    Next

    Me.ContextMenu = mnuPopUp

    The last line here assigns the new context menu to the form's ContextMenu property. The only reason you should do this is to make sure that a reference to the context menu is conveniently available when you need it - for example, in the form's event handler for a MouseDown event.

    In addition, VB.NET does not provide a PopupMenu command built into the language. Instead, you use the ContextMenu.Show() method, along with the coordinates where you want to display the context menu.

    VB .NET code:
    Private Sub Form1_MouseDown _
    (ByVal sender As System.Object, _
    ByVal e As _
    System.Windows.Forms._
    MouseEventArgs)
    Handles MyBase.MouseDown
    If e.Button = Mouse _ Buttons.Right Then
    Me.ContextMenu.Show(Me, _ New Point(e.X, e.Y))
    End If
    End Sub

    The VB 6 Solution
    You need to patch this problem manually. The best way to manage your context menus - and the best time to create them - is up to you. Once again, the Upgrade Wizard won't offer any help beyond identifying the problem.

    The Disappearing Modeless Window
    If you use a startup subroutine in VB.NET, your application will end as soon as the subroutine ends - even if other windows are still open. This is a significant change from VB 6, which only shuts down a program if you use the End command, or when all windows have been closed.

    For example, consider the migrated startup code below. It suffers from a fatal flaw: as soon as the window is displayed, the application ends prematurely, and the window is closed.

    VB .NET code:
    Public Sub Main()
    ' Program will end immediately _ after executing the next line.
    frmMain.DefInstance.Show()
    End Sub

    The VB 6 Solution
    In most cases the Upgrade Wizard will not make the required change automatically. However, it's fairly easy to correct manually. Just show all windows modally from the Main subroutine using the ShowDialog() method:

    VB .NET code:
    Public Sub Main()
    ' Program will end when _ frmMain is closed.
    frmMain.DefInstance.ShowDialog()
    End Sub

    Starting From Scratch
    There are also some types of code that the Upgrade Wizard won't attempt to translate at all. These include code routines for printing, using the clipboard, drawing shapes and lines directly on a form, and implementing context-sensitive help. In some cases, the required VB.NET code isn't necessarily that different. However, the Upgrade Wizard won't help you (it will simply comment the troublesome areas of code and leave them untouched and incompatible).

    In addition, there are some types of VB 6 projects that aren't fit for any type of migration:

  • An Internet project using Web Classes, ActiveX Documents, or DHTML: None of these development technologies is supported in .NET. Projects based on Web Classes can be upgraded to ASP.NET Internet projects, but are likely to provide many additional headaches.
  • A database project based on the Data Environment, which is also no longer supported.
  • A database project that uses data binding to other controls: These features can be upgraded under some circumstances, but will definitely fail with the DAO and RDO data access technologies.
  • An ActiveX control or ActiveX DLL project: While you can create .NET equivalents of these COM-based types of programs, you will lose all of their existing COM features. If you have controls or components that are still being shared among numerous applications, it will probably be easier to use them in .NET or to make additional .NET versions, rather than trying to migrate them and replace the originals.
  • A VB 5 program that hasn't made the transition to VB 6: For all its differences, VB 6 is still one step closer to VB.NET than any earlier release of Visual Basic. Make the change to VB 6 before getting more ambitious.

    Summary
    There are few easy solutions to the problems we've explored. With careful planning, you can code around some issues before you migrate (like the change in array support), but other problems are much more difficult to resolve (like printing routines, drawing logic, and the infamous control array).

    The best approach for many VB 6 projects is - scandalously enough - to not migrate at all. For components, you can make heavy use of .NET's COM interoperability, and migrate individual modules to .NET piecemeal when new development is needed. For stand-alone applications that don't use COM, you may be better off maintaining the project in the unmanaged world of VB 6 until a full rewrite is possible. In this case, ensuring that your program is designed using a well-documented object-oriented design is the key to recoding it in another language (like VB.NET).

    For more information about migration to VB.NET, you can read Microsoft's migration documentation at http://msdn.microsoft.com/library/en-us/ vbcon/html/vboriupgradingfromvisualbasic60.asp (which breezes over many of the real difficulties developers may face) or my own The Book of VB .NET, which provided some of the material for this article. Keep in mind that the world of COM and VB 6 is far from gone. The next few years of development will see new trails blazed in VB.NET, and existing code maintained alongside it in VB 6.

    Resource
    MacDonald, M. (2002) The Book of VB .NET. No Starch Press.

    Author Bio
    Matthew MacDonald is an author, educator, and MCSD developer in all things .NET. He's written about programming for O'Reilly, Osborne McGraw/Hill, Apress, and New Riders. You can read about his .NET books, including The Book of VB.NET, at his Web site, www.prosetech.com. matthew@prosetech.com

    All Rights Reserved
    Copyright ©  2004 SYS-CON Media, Inc.

      E-mail: info@sys-con.com