Common Migration Problems
In this section, we’ll examine some of the problems that can derail .NET migration.
If you can find these problems in your own Visual Basic 6 applications, it
may be a good idea to fix them within that environment before migrating the
project-or call off the migration process altogether.
TIP If you’re still developing projects in Visual Basic 6, the information in this section will
help you make choices that will facilitate migration in the future.
Arrays
Arrays can thwart any attempt at migration. Visual Basic .NET’s new insistence
that every array have a lower boundary of zero can cause all sorts of trouble. In
our original 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, updating them to comply with the zero-boundary requirement
would not be easy, and would requires hours of modification and retesting.
Some early Microsoft documents promised that the Upgrade Wizard would
create special array classes that will mimic normal arrays and allow any lower
boundary that you need. This flexibility never materialized in the beta or release
versions, and it’s uncertain whether or not the new array classes would help at
all, or just complicate life even more.
However, it’s fairly easy to create a Visual Basic 6 program that won’t suffer
from this array problem. The best choice is not to use zero-bounded arrays at
all, but to move straight to collections or other custom classes. In our
VB6OrderMaker application, the Visual Basic 6 code would be clearer and more
elegant if orders were stored in an Order class that contained a collection of
ordered items as a property (for example, Order.Items), and any other required
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.
Variants
Variants are a special Visual Basic 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.
In Visual Basic .NET, variants aren’t supported, but the System.Object type
provides the same flexibility. If Option Strict is disabled for your application (as
it is by default when you are migrating a project), you can use the generic object
type in almost the exact same way as you would variants, with the same automatic
conversion feature. If Option Strict is enabled, you need to convert object
types manually when performing operations with them. This is explained in
Chapter 7 of The Book of VB .NET, but a quick review is helpful:
Dim x As Object, y As Object, z As Object x = 1 y = 2 z = x + y ' Will only work in Option Strict is off. z = CType(x, Integer) + CType(y, Integer) ' Always works without a hitch.
The Upgrade Wizard will convert all variants into generic objects. After a
typical migration, you may find yourself with many more generic objects than
you expected. The problem is that even though variants are rarely used deliberately
in VB 6 code, variables could be inadvertently defined without data types,
as shown here:
' VB 6 code. Dim intA, intB As Integer ' intB is an Interger, but intA will be a variant.
This oversight, which occurs most often when simple counters and other
unimportant temporary variables are defined, causes Visual Basic 6 to use its
default data type, which is the variant. During the migration of the VB6Order-
Maker program into .NET format, many unspecified counter variables ended up
as objects. The Upgrade Wizard flagged every subsequent line of code that performs
any operations with these variables, indicating that it can’t determine the
default properties for those objects:
Dim intA As Integer, intB As Object intA = 0
' UPGRADE_WARNING: Couldn't resolve default property of object intB. ' Click for more: ms-help://MS.MSDNVS/vbcon/html/vbup1037.htm intB = 0
This minor issue isn’t a problem-in fact, this portion of the code will still
work perfectly. However, this idiosyncrasy led to a large number of additional
warnings in the VB6OrderMaker upgrade report.
Default Properties
Why is the Upgrade Wizard so aggressive in flagging the unstructured use of an
object? In VB6OrderMaker, these objects really represent simple value types,
and the default value is automatically available as long as Option Strict is Off.
However, this type of operation could create a problem in other circumstances.
In .NET, standard default properties aren’t supported, and attempts to use
an object’s default property will fail. Usually, the Upgrade Wizard will add the
necessary information to qualify a default property (for example, change
txtBox = “Text” to txtBox.Text = “Text”). However, when you have a mysterious
late-bound type that’s defined only as an object, this trick fails. For example,
if x, y, and z were real objects with numerous properties (such as x.Value),
the statement z = x + y wouldn’t work. Visual Basic .NET would have no way
of knowing what properties to use for this calculation.
The solution to this problem is simple. When programming in Visual
Basic 6, be careful to always define data types. Also, don’t use late-bound
objects. Not only are they slower, because VB has to inspect them before they
are used, but they may lead to migration headaches.
Load Statement
In Visual Basic 6, you could use the Load statement to create a form without
displaying it:
' VB 6 code. Load frmMain
In Visual Basic .NET you can accomplish the same sort of thing by creating
an instance of your form class, but not displaying it.
Dim frm As New frmMain()
Both of these techniques allow you to pre-configure the form and its controls
before displaying the form. In VB6OrderMaker, Load statements are used
extensively to provide Wizards that automatically load other forms, use their
objects and procedures to calculate totals and create order items, and then
unload them. This approach is well organized, but not nearly as efficient or easy
to use as a real class- based design. It also leads to migration errors, because the
Load statement is not automatically upgraded.
There is at least one easy way to replace the Load statement in a migrated
project:
frmMain.DefInstance
This code initializes the form and makes it available through the shared
DefInstance property.
Printing
The Upgrade Wizard is not able to update printing code. During the migration
of VB6OrderMaker, the code used to select the printer, configure device settings,
and output an order was flagged with error messages. The only way to
resolve such problems is to rewrite the code for the new PrintDocument object
in the .NET class library. If you’ve spent hours generating custom formatted output
in your original application, you will not enjoy the migration process.
The Clipboard
Code that interacts with the Windows clipboard will also fail to work after a
migration. Once again, the Upgrade Wizard leaves the hard work to you, and you
need to rewrite the code with the .NET equivalent. In VB6OrderMaker, the clipboard
was used to transfer information into the RichText control for a preview.
Clearly, a better approach is to use .NET’s new PrintPreview control. Unfortunately,
there’s no migration path between the two. If you are still working on a
VB 6 project, you have no easy way to use print preview features similar to those
available in VB .NET, and you will have to resort to third-party components or
nontraditional approaches, such as those found in VB6OrderMaker. In .NET,
however, there’s really no reason not to use the bundled PrintPreview control.
Context-Sensitive Help
The HelpContextID property is not supported in Visual Basic .NET. If you want
to create context-sensitive help, you’ll need to use the HelpProvider control,
which was discussed in Chapter 4 of The Book of VB .NET. This control provides a number of minor
enhancements, but once again, the Upgrade Wizard won’t help you make the
coding changes. Any program-VB6OrderMaker, for instance-that makes significant
use of context-sensitive help will need at least some rewriting.
Menu Controls
In Visual Basic .NET, you can’t use the same menu component for an application
(pull-down) menu and a context menu. In Visual Basic 6, you had to create
a pull-down menu before you could use it in a context menu. Microsoft schizophrenia
once again?
The VB 6 code used to display a context menu looked like this:
' 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
When importing a project that uses context menus, the Upgrade Wizard
leaves the PopUpMenu command in its original state, and flags it as an error.
Before you can make it work, you have to create a new ContextMenu object.
Then, you have to copy the ordinary menu information into the ContextMenu:
Dim mnuPopUp As New ContextMenu() Dim mnuItem As MenuItem
For Each mnuItem In mnuOrderOptions.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. Because the current form is
always available through the Me keyword, it provides a convenient place to
attach the ContextMenu reference.
The code for displaying a context menu in VB .NET is similar to that used
in VB 6, but not exactly the same:
Private Sub Form1_MouseDown(ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown If e.Button = MouseButtons.Right Then Me.ContextMenu.Show(Me, New Point(e.X, e.Y)) End If End Sub
The best way to manage your context menus, and the best time to create
them, are up to you. Once again, the Upgrade Wizard won’t offer any help
beyond identifying the problem.
Control Arrays
Control arrays present a particularly unpleasant example of what can go wrong
with migration. In Visual Basic 6, control arrays were often the best way to solve
problems. With a control array, numerous similar controls are placed into an
array. 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
In .NET, this approach can be replaced in various different incompatible
ways, including data binding, new list controls, or collection classes. Control
arrays, however, aren’t supported.
Control arrays also allowed a program to use a single event handler for
numerous similar controls, which was 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, as discussed in
Chapter 4 of The Book of VB .NET.
Private Sub Highlight(ByVal sender As Object, ByVal e As MouseEventArgs) _ Handles lblLine1.MouseMove, lblLine2.MouseMove ' You can add more events to the Handles list, ' or use the AddHandler statement instead. Dim lbl As Label = CType(sender, Label) lbl.ForeColor = Color.RoyalBlue End Sub
To summarize: Control arrays were a quirky if 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. There’s no easy way to design around it in
VB 6, since control arrays are often a good design approach in that environment.
Unfortunately, this is one of the Upgrade Wizard’s fundamental limitations.
Automatic Re-initialization
In Visual Basic 6, if you defined an object with the New keyword, it had the
strange ability to automatically recreate itself:
' VB 6 code. Dim objPerson As New Person Person.Name = "John" Person = Nothing ' The Person object is destroyed. Person.Name = "John" ' At this point, an empty Person object is reinitialized.
This was generally not what programmers expected, and it led to quirky
errors and memory waste. In Visual Basic .NET, the New keyword simply allocates
space for the object; it does not cause any unusual re- initializing behavior.
If you’ve made use of this trick, either deliberately or unwittingly in VB 6,
the code won’t work in .NET. When you try to use the destroyed object, you
will receive a null reference error. You’ll have to rewrite the preceding example
like this:
Dim objPerson As New Person() Person.Name = "John" Person = Nothing ' The Person object is destroyed.
Person = New Person() Person.Name = "John"
Re- initialization may be a minor detail, but it’s also one more potential
migration headache, particularly if the project you want to import has not been
created using best practices.
GDI
Visual Basic 6 had built in methods for drawing circles and other shapes directly
onto a form. In Visual Basic .NET, these graphical routines have been enhanced
and replaced by the GDI+ library, which you can access through the
System.Drawing namespaces. Once again, any original code you may have written
will need to be scrapped. This includes palette management, which
VB6OrderMaker used to ensure that the splash screen is displayed properly on
older 256-color monitors.
Class_Terminate Code
In Visual Basic 6, you might have used the Terminate event to perform automatic
cleanup tasks, such as closing a database connection or deleting a temporary
file. In Visual Basic .NET, this technique is a guaranteed to cause problems,
because the .NET framework uses non-deterministic garbage collection.
Garbage collection in VB .NET works in much the same way that garbage
collection works in many actual communities. In most neighborhoods, residents
can pinpoint the time that garbage is placed into the appropriate receptacle, but
they really have no idea when someone will be motivated to take it out. Similarly,
in .NET garbage collection may be put off until the system is idle, and
can’t be relied upon to release limited resources. You might find other surprises
if you use the Terminate event to try and interact with other forms in your
application. Generally, these techniques won’t work in VB .NET.
As with control arrays, a substantial difference in programming philosophies
underlies this problem. In Visual Basic 6, using the Terminate method was
a useful approach to making sure that cleanup was always performed the
moment the class was released. In Visual Basic .NET, you are better off adding a
Dispose method, and relying on the programmer to call this method after using
an object and just before destroying it. The Upgrade Wizard tries to encourage
this change: It places code from the Terminate event into a new, separate
method so that you can easily call it when needed. It also overrides the Finalize
method and adds a call to the new method to ensure that cleanup is performed:
Public Sub Class_Terminate_Renamed() ' Code goes here. End Sub
Protected Overrides Sub Finalize() Class_Terminate_Renamed() MyBase.Finalize() End Sub
This a good start, but you would be better off putting the code in a method
called Dispose (rather than Class_Terminate_Renamed, as the Upgrade Wizard
uses in our example). You’ll also still need to modify the code that uses the
class, because you’ll want to make sure that it calls the Dispose method before
destroying the object.
VarPtr, StrPtr, ObjPtr
These undocumented functions have traditionally been used by Visual Basic
experts to find the memory addresses where variables or objects were stored.
These functions allowed addresses to be passed to a DLL routine or to the Windows
API, which sometimes required this information. In Visual Basic .NET, you
hopefully won’t need to have this kind of low-level access to memory information,
as it complicates programs 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:
' You will need to import the namespace shown below to use this code as written. ' Imports System.Runtime.InteropServices
Dim MyGCHandle As GCHandle = GCHandle.Alloc(MyObj, GCHandleType.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()
Of course, the coding you use is up to you, but be aware that the Upgrade
Wizard will simply place an error flag if it finds the unsupported VarPtr, StrPtr,
or ObjPtr function.
Memory Storage for User-Defined Types
In Visual Basic 6, you could assume that user-defined types represented a contiguous
block of memory. This is not true for the .NET equivalent, structures,
which use a more efficient allocation of space. This change only has an effect if
you are using low-level DLLs or COM components (for instance, a legacy database
that expects data to be passed as a block of memory). To resolve this issues,
you’ll have to delve into some of .NET’s advanced data type marshalling features,
which are beyond the scope of this book, but detailed in the MSDN class
library reference under the System.Runtime.InteropServices namespace.
Optional Parameters
Optional parameters in VB .NET require default values, as explained in Chapter
3 of The Book of VB .NET. This is a relatively painless change. However, one consequence is that you
can’t rely on IsMissing to tell if a value has been submitted (although you can
check for your default value or use IsNothing, which is the Upgrade Wizard’s
automatic change). Keep in mind that overloaded functions often yield better
.NET options than do optional values.
Goto and Gosub
Support for these statements was scaled down in early beta versions of VB .NET,
but has been added back for the final release. But even though you can doesn’t
mean you should! Goto and Gosub are archaic programming concepts, and
using them is a direct road to programming nightmares. It’s a surprise that they
remained all the way through Visual Basic 6, let alone made the jump to the
.NET world.
Other New Behaviors
“New behavior” is the term that the Upgrade Wizard uses in its upgrade report
when it makes a change from one property or method to another that is similar,
but not identical. In many cases, this warning just represents an improvement to
a control. In other situations, it may represent a deeper problem that requires
significant reworking.
Most experienced Visual Basic developers have spent significant time learning
their favorite controls, and know how to exploit all the associated quirks and
idiosyncrasies. This poses a problem for migration, where optimized code might
not work at all (or worse, might appear to work, but will then cause problems
under unusual future circumstances).
NOTE This issue raises a larger question. Namely, when is it safe to migrate a project? Even if
the process appears to succeed, you will need hours of testing to verify its success before you
can trust the updated application enough to release it into a production environment.
Preparing for VB .NET
Are the migration features in Visual Basic .NET impressive or depressing? It
really depends on how you look at it. Certainly, they represent a minor technological
feat. At the same time, many developers argue that they are useless for
using real VB 6 applications in the .NET environment. Even if you can import a
project, the result may be a rather ugly mess of old concepts, dressed up with
workarounds added by the Upgrade Wizard. This kind of application can be difficult
to work with and enhance, thus defeating the purpose of migration.
If you’re working on a Visual Basic 6 project today, your best choice is to
make rigorous use of class-based designs. If you take this protective step, then
even if you can’t import an entire project into .NET, you will at least be able to
import or recreate all of your business objects and data classes. Generally, data
classes are much easier to import than other components, because they don’t use
such VB 6-specific features as file or printer access, and they don’t directly create
user interface or require forms support. You can then import your business
objects, add a new .NET user interface tier (taking advantage of the latest features
in Windows Forms), and add lower-level data access components, if necessary, to
support ADO.NET or .NET file access through streams. The ability to upgrade is
one of the remarkable benefits of a structured three-tier design. The more you
can break your application down into functional units, the better chance you’ll
have to reuse at least some of its elements in the .NET environment.
What Comes Next?
This article has shown both the beauty and the ugliness of backward compatibility.
Visual Basic .NET is at its most elegant when dealing with COM interoperability,
allowing you to work with most components and even drop ActiveX controls
into your application without a second thought. However, the picture sours
if you need to import an average, mid-sized Visual Basic project, which is almost
guaranteed to crumble in the face of numerous incompatibilities.
As always, remember that just because a program is created in an older version
of Visual Basic it does not mean it needs to be brought to .NET. These
“legacy” programs will be supported on the Windows platform for years to
come. Conversely, just because an ActiveX control can be inserted into your
project doesn’t mean that you can’t reap more benefits by trying to achieve the
same effect using the class library. However, you’re likely to have at least some
third-party ActiveX controls that you don’t want to abandon in certain situations.
Remember to consider the tradeoff-if converting to .NET requires that
you import dozens of COM components, then you may be better off maintaining
your program in unmanaged Visual Basic 6.
If you are a COM guru (or are planning to become one), you will probably
want to explore .NET’s advanced COM support features. These include tools for
handling COM interoperability on your own, such as the System.Windows.
Forms.AxHost class and the types in the System.Runtime.InteropServices.
Be forewarned: Unless you are a war-hardened COM veteran, you won’t be able
to do much with these namespaces that you can’t do even better with the automatic
wrapper class generation features in Visual Studio .NET.
Matthew MacDonald, an author, educator and MCSD developer, has worked with Visual Basic and ASP since their inceptions. He is the author of The Book of VB .NET (No Starch Press) and ASP.NET: The Complete Reference (Osborne McGraw-Hill) and co-author of Programming .NET Web Services (O’Reilly).