Conclusion
This chapter focused on ten common problems involving features within your upgraded application. Hopefully, the discussion has given you a better handle on how to resolve similar issues in your own applications. The samples provided on the companion CD should allow you to see how isolated feature areas can be upgraded. The
Chapter 11
Resolving Issues with Language
The Basic programming language has been around for a long time in a variety of forms—GW-BASIC,
QuickBasic
,
Visual Basic
, and Visual Basic for Applications (VBA), to
In Visual Basic .NET you will not find Type…End Type , GoSub…Return , While…Wend , nonzero-based arrays, the Currency type, static subroutines, or fixed-length strings. You will, however, find new language elements such as Structure…End Structure , Inherits , Overloads , Try…Catch , SyncLock , the Char type, the Decimal type, and assignment shortcuts such as += and -= to increment or decrement a value. New statements such as Inherits , Overloads , Try…Catch , and SyncLock provide support for new features—inheritance, structured exception handling, and multithreading—not found in previous versions. Other changes such as the removal of GoSub…Return , the change from Type…End Type to Structure…End Structure , and the addition of the Char type are intended to modernize the language. Still other changes—removal of support for nonzero-based arrays and fixed-length strings—are the result of trade-offs needed to make Visual Basic .NET work with other .NET languages.
This chapter discusses upgrade issues
Language Elements
Let’s look first at language-related upgrade issues, setting aside for now issues
#If…#End If Precompiler Statements
All conditional compilation statements are
#Const COMMENT = False #If COMMENT Then This is my invalid code. There is no comment. This will only confuse the Upgrade Wizard if ittries to upgrade me. #End If
You should make sure that you have turned on all the conditional compilation arguments in your Visual Basic 6 project for the code paths you want to upgrade. Also be sure that your code compiles and runs in Visual Basic 6. The wizard will not be able to upgrade invalid code contained in your project. This includes code contained within an #If…#End If block that evaluates to True .
Constants and Constant Expressions
If you define a constant (in Visual Basic 6) to represent a property on a Windows form or control, you may find that the code does not compile after you upgrade the project. For example, suppose that your Visual Basic 6 project contains the following code:
Const COLOR_RED = vbRed
The Upgrade Wizard will upgrade the code to the following:
'UPGRADE_NOTE: COLOR_RED was changed from a Constant to a Variable. Dim COLOR_RED As System.Drawing.Color = System.Drawing.
Color
.Red
Why was the
Const
declaration changed to a
Dim
? The reason is that
System.Drawing.Color.Red
, although it looks like a constant, is not; it is a nonconstant expression. This means that, rather than being a constant value, such as 255 (the value of
vbRed
), it is a property that returns a
System.Drawing.Color
object. To see the declaration for
System.Drawing.Color.Red
within the upgraded Visual Basic .NET project, right-click Red in the Visual Basic .NET code window and choose Go To Definition. The Object Browser will display the definition of
System.Drawing.Color.Red
, as
Public Shared ReadOnly Property Red As System.Drawing.Color
In some cases, you may encounter a compiler error in your upgraded code. For example, suppose that in Visual Basic 6 you create an enumerator containing values for red, green, and blue called RGBColors . You use the enumerator values in code to combine red and green to create a yellow background for your form, as follows:
Enum RGBColors Red = vbRed Green = vbGreen Blue = vbBlue End Enum Private Sub Form_Load() BackColor = RGBColors.Red + RGBColors.Green End Sub
The resulting Visual Basic .NET code after upgrade will be as follows:
'UPGRADE_ISSUE: Declaration type not supported: Enum member 'of type Color. Enum RGBColors Red = System.Drawing.Color.Red Green = System.Drawing.Color.Lime Blue = System.Drawing.Color.Blue End Enum Private Sub Form1_Load(ByVal eventSender As System.Object, _ ByVal eventArgs As System.EventArgs) _ Handles MyBase.Load BackColor = System.Drawing.ColorTranslator.FromOle(RGBColors.Red + _ RGBColors.Green) End Sub
Because the declarations for the color objects Red , Lime , and Blue are not constant, each line results in the compiler error “Constant expression is required.”
There are a couple of ways that you can fix up the code to work. You can define your own constant values, or you can use nonconstant values directly, as described in the sections that follow.
Define Your Own Constant Values
The
Const vbRed As Integer = &HFF Const vbGreen As Integer = &HFF00 Const vbBlue As Integer = &HFF0000 Enum RGBColors Red = vbRed Green = vbGreen Blue = vbBlue End Enum Private Sub Form1_Load(ByVal eventSender As System.Object, _ ByVal eventArgs As System.EventArgs) _ Handles MyBase.Load BackColor = System.Drawing.ColorTranslator.FromOle(RGBColors.Red + _ RGBColors.Green) End Sub
Use Nonconstant Values Directly
Another way to solve the problem is to replace any instances in which a member of
RGBColors
, such as
RGBColors.Red
, is used with the matching
System.Drawing.Color
, such as
System.Drawing.Color.Red
. When you make this change, a new question arises: How do you manage calculations that involve two objects? For example, in the example above, how do you add the values of two color objects together to
In the case of color objects, you can obtain a meaningful numeric (color) value by using the
ToOle
method of the
System.Drawing.ColorTranslator
class. The
ToOle
method takes a color object—such as
System.Drawing.Color.Red—
and obtains an RGB color value for it.
ToOle(System.Drawing.Color.Red)
, for example, will return the RGB value 255, or FF in hexadecimal. Not by
'Include the Imports statement at the top of the Form file Imports System.Drawing … Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load BackColor = _ ColorTranslator.FromOle(ColorTransltor.ToOle(Color.Red) _ + ColorTranslator.ToOle(Color.Lime)) End Sub
It’s up to you whether you want to define your own constants or use the nonconstant values directly in your code. If, in your original code, you are using the constant values in combination with other constant values—say, in a numeric calculation—it would make sense to define your own constant values and use the constants in your code. Otherwise, if you are making a simple assignment of a constant value to a property, it would make more sense to use the nonconstant value—in other words, the object—directly.
Control Flow
If you never knew that Visual Basic supported
GoSub…Return
,
On…GoTo
, and
On…GoSub
, and you don’t use these statements in your Visual Basic 6 code, you’re ahead of the game. Visual Basic .NET
GoSub…Return
GoSub…Return is a holdover from earlier generations of the Basic language that did not support subroutines, Sub and Function . Visual Basic .NET does not support the GoSub statement. It does, however, support the Return statement, but its meaning is different from that in Visual Basic 6. Return is used to return from a subroutine. You can use the Return statement to return a value in a function.
If you are using the
GoSub
statement in your code, we recommend copying the code associated with the
GoSub
label to its own subroutine. Any local
Sub Main() Dim Animals(2) As String Dim ctAnimal As Long Dim PrevAnimal As String Dim i As Long Animals(0) = "Alligator" Animals(1) = "Monkey" Animals(2) = "Monkey" For i = 0 To UBound(Animals) ' Detect break in sequence and show summaryinfo If PrevAnimal <> "" And PrevAnimal <> Animals(i) Then GoSub ShowDetail End If ctAnimal = ctAnimal + 1 PrevAnimal = Animals(i)Next ' Show summary info for last animal in list GoSub ShowDetail Exit Sub ShowDetail: MsgBox PrevAnimal & " " & ctAnimal ctAnimal = 0 Return End Sub
The Upgrade Wizard upgrades the code as is, inserting UPGRADE_ISSUE comments for each occurrence of GoSub and UPGRADE_WARNING comments for each occurrence of Return . The comments tell you that GoSub is not supported and that Return has a new behavior in Visual Basic .NET.
You can replace the
GoSub
statements with calls to a subroutine called
ShowDetail
. You need to pass the local variables as parameters to the
ShowDetail
subroutine. In the previous example, you would pass
PrevAnimal
and
ctAnimal
to
ShowDetail
. You need to pass
ctAnimal
ByRef
so that the value gets reset to 0 when execution returns from
ShowDetail
. The following code
Public Sub Main() Dim Animals(2) As String Dim ctAnimal As Integer Dim PrevAnimal As String Dim i As Integer Animals(0) = "Alligator" Animals(1) = "Monkey" Animals(2) = "Monkey" For i = 0 To UBound(Animals) ' Detect break in sequence and show summary info If PrevAnimal <> "" And PrevAnimal <> Animals(i) Then ShowDetail(PrevAnimal, ctAnimal) End If ctAnimal = ctAnimal + 1 PrevAnimal = Animals(i) Next ' Show summary info for last animal in list ShowDetail(PrevAnimal, ctAnimal) End Sub Sub ShowDetail(ByVal Animal As String, ByRef ctAnimal As Integer) MsgBox(Animal & " " & ctAnimal) ctAnimal = 0 End Sub
On…GoTo
On…GoTo
is an
The following Visual Basic 6 example demonstrates the use of On…GoTo .
Dim Mode As Integer Mode = 2 'WriteMode On Mode GoTo ReadMode, WriteMode MsgBox "Unexpected mode" Exit Sub ReadMode: MsgBox "Read mode" Exit Sub WriteMode: MsgBox "Write mode"
The Upgrade Wizard upgrades the code to use Select Case and GoTo as follows:
Dim Mode As Short Mode = 2 'WriteMode Select Case Mode Case Is < 0 Error(5) Case 1 GoTo ReadMode Case 2 GoTo WriteMode End Select MsgBox("Unexpected mode") Exit Sub ReadMode: MsgBox("Read mode") Exit Sub WriteMode: MsgBox("Write mode") End Sub
Although the wizard produces correct code, you will probably want to move the code contained under each label to each Case statement. This step will eliminate the need for GoTo and will also condense your Visual Basic .NET code as follows:
Dim Mode As Short Mode = 2 'WriteMode Select Case Mode Case 1 MsgBox("Read mode") Case 2 MsgBox("Write mode") Case Else MsgBox("Unexpected mode") End Select
On…GoSub
On…GoSub works in much the same way as On…GoTo , except that when you jump to the label, execution can return back to the next statement after the On…GoSub statement when you call Return . The following example, in particular the UpdateAccountBalance subroutine, demonstrates the use of On…GoSub to jump to a transaction—deposit or withdrawal—depending on the value of the passed-in Transaction variable. The Transaction variable can either be 1 for deposit or 2 for withdrawal. If, for example, the value is 1, the On…GoSub statement jumps to the first label specified in the list—in this case Deposit :
Option Explicit Private m_AccountBalance As Currency Private Sub Main() Const Deposit = 1 Const Withdrawal = 2 m_AccountBalance = 500 UpdateAccountBalance Deposit, 100 End Sub Private Sub UpdateAccountBalance(ByVal Transaction As Integer, _ ByVal Amount As Currency) On Transaction GoSub Deposit, Withdrawal MsgBox "New balance: " & m_AccountBalance Exit Sub Deposit: m_AccountBalance = m_AccountBalance + Amount Return Withdrawal: m_AccountBalance = m_AccountBalance - Amount Return End Sub
As in the On…GoTo example given earlier, you can change your On…GoSub code to use a Select Case statement.
Select Case Transaction Case Deposit m_AccountBalance = m_AccountBalance + Amount Case Withdrawal m_AccountBalance = m_AccountBalance - Amount End Select MsgBox "New balance: " & m_AccountBalance
File Functions
Visual Basic 6 includes a number of language statements—such as Open , Close , Put # , Get # , and Print # — that allow you to read and write text and binary files. Visual Basic .NET provides a set of functions that are compatible with the Visual Basic 6 file statements. The Upgrade Wizard will upgrade your Visual Basic 6 file statements to the equivalent Visual Basic .NET functions. Table 11-1 illustrates those relationships.
|
Visual Basic 6 Statement |
Visual Basic .NET Function |
|
ChDir |
ChDir |
|
ChDrive |
ChDrive |
|
Close |
FileClose |
|
FileCopy |
FileCopy |
|
Get |
FileGet |
|
Input # |
Input |
|
Kill |
Kill |
|
Line Input # |
LineInput |
|
Lock |
Lock |
|
MkDir |
MkDir |
|
Open |
FileOpen |
|
Print # |
Print , PrintLine |
|
Put |
FilePut |
|
Reset |
Reset |
|
RmDir |
RmDir |
|
SetAttr |
SetAttr |
|
Unlock |
Unlock |
|
Width # |
FileWidth |
|
Write # |
Write , WriteLine |
File Format Compatibility
When you upgrade an application from Visual Basic 6 to Visual Basic .NET, not only do you need to worry about getting your Visual Basic .NET application working in a compatible way, but you also need to be
Print , PrintLine , Write , and WriteLine
The Visual Basic .NET
Print
and
PrintLine
functions are equivalent to the Visual Basic 6
Print #
function. Which function you use depends on how
Print #
is used in your Visual Basic 6 application. If the text you are printing is
Print #1, "This is a full line of text" Print #1, "This is a partial line of text"; Write #2, "This is a full line using write" Write #2, "This is a partial line using write";
is equivalent to the following Visual Basic .NET code:
PrintLine(1, "This is a full line of text") Print(1, "This is a partial line of text") WriteLine(2, "This is a full line using write") Write(2, "This is a partial line using write")
The Upgrade Wizard automatically upgrades your Visual Basic 6 Print # and Write # code to use the appropriate function, based on whether the text is terminated by a semicolon. It is when you start editing your upgraded Visual Basic .NET code or writing new code that you need to be aware that there are two separate functions that give you full-line versus partial-line file printing.
When Reading or Writing Variables,
If you are reading or writing information to a binary file, you need to pay attention to the variables you are using. For example, the byte size of Integer and Long variables is different between Visual Basic 6 and Visual Basic .NET. An Integer is 16 bits in Visual Basic 6 and 32 bits in Visual Basic .NET; a Long is 32 bits in Visual Basic 6 and 64 bits in Visual Basic .NET. If, in Visual Basic .NET, you are reading data from a binary file that was written as a Visual Basic 6 Integer , you will need to use a variable of type Short (16 bits) to read the data.
If you use the Upgrade Wizard to upgrade your application, this change will not be an issue because the wizard automatically maps all your variables that are type Integer to Short and maps type Long to Integer . However, when you edit the upgraded code or write new code, you need to be careful to use the correct data type—based on size, not name—when reading and writing binary files. This includes files that you open using Random mode.
Random-Access Files and Dynamic Arrays
If you create a random-access binary file in Visual Basic 6, additional header information relating to the format of your data may be written to the file. This header information
Dynamic Array Refresher
Not sure what a dynamic array is? You are probably not alone. The difference between a dynamic array and a fixed-length array involves a subtle variation in syntax. If your declaration for an array does not specify the size, it is
Dim MyDynamicArray() As Integer
Once the array is declared as a dynamic array, it is always considered a dynamic array, even after you redimension it to a known size, as in the following statement:
ReDim MyDynamicArray(100)
If you specify the size of the array as part of the declaration, it is considered to be a fixed-length array. For example,
Dim MyDynamicArray(100) As Integer
Once you have dimensioned a fixed-length array, you cannot use the ReDim statement to change its size.
The Upgrade Wizard does not handle situations that involve writing the contents of a dynamic array to a random-access file. Consider the following Visual Basic 6 code, which initializes the contents of a dynamic array, OutputData , with its own index values.
Dim OutputData() As Byte ReDim OutputData(100) Dim i As Integer For i = 0 To UBound(OutputData) OutputData(i) = i Next Open Environ("TEMP") & "\ArrayData.Dat" For Random As #1 Len = 120 Put #1, , OutputData Close #1
If we use the following Visual Basic 6 code to read in the contents of the array, everything works fine:
Dim OutputData() As Byte Dim i As Integer Open Environ("TEMP") & "\ArrayData.Dat" For Random As #1 Len = 120 Get #1, , OutputData Close #1 For i = 0 To UBound(OutputData) If i <> OutputData(i) Then MsgBox "Error: Data element (" & i & ") is incorrect. " & _ "Value=" & OutputData(i) Exit For End If Next
If you upgrade this Visual Basic 6 code to Visual Basic .NET, the resulting code is as follows:
Dim OutputData() As Byte Dim i As Short FileOpen(1, Environ("TEMP") & "\ArrayData.Dat", _ OpenMode.Random, , , 120) 'UPGRADE_WARNING: Get was upgraded to FileGet and has a new behavior. FileGet(1, OutputData) FileClose(1) For i = 0 To UBound(OutputData) If i <> OutputData(i) Then MsgBox("Error: Data element (" & i & ") is incorrect. " & _ "Value=" & OutputData(i)) Exit For End If Next
If you run this upgraded code, the first problem you will encounter is a “Cannot determine array type because it is Nothing” exception on the line
FileGet(1, OutputData)
. This exception occurs for exactly the reason that it indicates: the
OutputData
array is
To fix this problem, you need to redimension the array with at least one element so that it is initialized to a value other than Nothing . Include the following statement after the Dim OutputData() As Byte statement:
ReDim OutputData(0)
Run the code again, and you uncover another problem. A message box pops up telling you “Error: Data element (0) is incorrect. Value=1.” This message occurs because the Visual Basic .NET FileGet statement assumes that the data it is reading was written from a fixed-length array, not a dynamic array. The value of 1 that leads to the error is actually a value contained within the header information for the dynamic array. It is the number of dimensions contained in the dynamic array, not the dynamic array data itself.
To fix this problem, you need to tell the FileGet statement that the data it is reading is coming from a dynamic array. You do this by specifying the optional FileGet parameter called ArrayIsDynamic . Change the following line in the Visual Basic .NET upgraded code:
FileGet(1, OutputData)
to
FileGet(1, OutputData, ArrayIsDynamic:=True)
Run the code again, and it will work as expected. The FileGet function will expand the OutputData array to have an upper bound of 100 and will read in the array contents, following the array header information, from the file.
Random-Access Files and Fixed-Length Strings
A problem similar to the dynamic array problem discussed in the previous section will occur if you attempt to read a fixed-length string from a random-access file. Consider the following upgraded Visual Basic .NET code to read in a fixed-length string:
Dim OutputData As New VB6.FixedLengthString(9) Dim i As Short FileOpen(1, Environ("TEMP") & "\StringData.Dat", _ OpenMode.Random, , , 15) 'UPGRADE_WARNING: Get was upgraded to FileGet and has a new behavior. FileGet(1, OutputData.Value) FileClose(1) For i = 1 To Len(OutputData.Value) If CStr(i) <> Mid(OutputData.Value, i, 1) Then MsgBox("Error: Character (" & i & ") is incorrect. Value=" & _ Mid(OutputData.Value, i, 1)) Exit For End If Next
This situation is different from the dynamic array case in that the FileGet function assumes that the data contained within the file is a variable-length string, not a fixed-length string. This means that the FileGet function expects the file to contain a string header giving the length of the string. If you execute the code, you will encounter a “Bad record length” exception. The FileGet function is getting tripped up because it thinks the first 4 bytes of data is the string length when in fact it is the string data. The function attempts to use the first 4 bytes of string data as the length, comes up with a large number—larger than the record size—and throws an exception.
To address this problem, the FileGet function provides an optional parameter called StringIsFixedLength . Set the parameter to True , and the code will work as expected. To fix the Visual Basic .NET code, change the following line:
FileGet(1, OutputData.Value)
to
FileGet(1, OutputData.Value, StringIsFixedLength:=True)