EWIs

The following section provides useful information about the messages (comments) that will appear on your upgraded code and how to solve them.

Working with EWIs

The number of EWIs in your code depends entirely on your programming style and the size, type and complexity of files that you want to upgrade. As a general rule, forms usually have more issues than classes and modules, because forms have a richer object model and significant transformations must be applied when upgrading to Windows Forms.

Steps to fix a migration issue

This section shows the recommended steps to fix an error related to EWIs. The steps are:

Locate the source of the problem

To find the source of the problem, you can add some breakpoints a few lines before the code where the exception or the different behavior manifests itself. You need to do this in both the original VB6 code and the upgraded code so that you can compare to find out what is causing the problem or why the problem is occurring.

Understand the code purpose in the original VB6 application

The next step is to understand, in the original VB6 application, the functionality of the section of the code causing the problem. You can perform the following activities to understand the original behavior:

  1. Create small examples of code that use the same features.

  2. Execute step by step (debug mode), as many times as you need, the section of code causing the problem.

Identify a solution in .NET

Once the functionality of the original VB6 application is understood, the next step is to replicate the same functionality in the upgraded .NET application. Some recommendations for this are:

  1. Create small examples in .NET to replicate the VB6 functionality.

  2. Review the related .NET documentation on the web to determine how to achieve the same functionality in .NET.

  3. Ask your peers or a Mobilize.NET migration expert.

Implement the solution

If you already have a solution, you can implement it. Once you have modified the upgraded code and the found solution was implemented, you need to test your code to confirm that the behavior is the same as the original VB6 application. You can, also, review the rest of the upgraded code to determine if the same problem is present in other areas.

Mobilize.NET Customization (optional)

In some cases, you can consider Mobilize.NET Customization. Several factors must be taken into account at this point:

  1. Timing

    • Will the current timeframe of the project allows the Mobilize Team to implement the required customization? Is the impact of the error large enough to justify it to be reported in order to be included in future releases used in future projects?

  2. Complexity

    • Is there an opportunity to make the replacement in several places using another automated solution such as Regular Expressions?

  3. Rework required

    • Are the manual changes performed at this point in the project so many that executing a code merge will imply too much additional work?

Document the solution

Once the solution is implemented and the problem is resolved, it is important that the solution is properly documented and stored in a shared knowledge base. This helps in cases where other developers run into the same issue and are able to access information on how to solve it.

EWIs Lists

In the following lists, you can see the most common EWIs. It is important to know that there are many other EWIs not described in those lists.

Warnings

The following are some of the most common warnings generated by the Visual Basic Upgrade Companion.

Code number

Description

206

Untranslated statement in %1. Please check source code

1003

ParamArray %1 was changed from ByRef to ByVal

1044

Sub Main in a DLL won't get called

1047

Application will terminate when Sub Main() finishes

1049

Use of Null/IsNull() detected

1063

Arrays in structure %1 may need to be initialized before they can be used

1068

%1 of type %2 is being forced to %3

2050

%1 Event %2.%3 was not upgraded

2065

%1 %2 %3.%4 has a new behavior

2074

%1 %2 %3.%4 was upgraded to %5.%6 which has a new behavior

2077

Change the default 0 index in the Rows property with the correct one

2080

%1 was upgraded to %2 and has a new behavior

2081

%1 has a new behavior

2099

Return value for Dir has a new behavior

6002

UserControl Event %1 is not supported

6021

Casting 'int' to Enum may cause different behavior

6022

The CommonDialog CancelError property is not supported in .NET

7005

Parameters (if any) must be set using the Arguments property of ProcessStartInfo

7006

The named argument %1 was not resolved and corresponds to the following expression %2

7008

The ProgId could not be found on computer where this application was migrated

7009

Multiples invocations to ShowDialog in Forms with ActiveX Controls might throw runtime exceptions

8007

Trying to marshal a non Blittable Type (%1). A special conversion might be required at this point. Moreover use 'External Marshalling attributes for Structs' feature enabled if required

Issues

The following are some of the most common issues generated by the Visual Basic Upgrade Companion.

ToDos

The following are some of the most common ToDos generated by the Visual Basic Upgrade Companion.

Notes

The following are some of the most common Notes generated by the Visual Basic Upgrade Companion.

Warnings

206 - Untranslated statement in %1. Please check source code

Description

This warning indicates an unexpected error during the automated upgrade process. As a result, some of the VB6 source code was not upgraded.

Recommendations

  • Please report the issue to Mobilize Support.

  • If the affected code is simple enough, you can manually write the missing code.

1003 - ParamArray %1 was changed from ByRef to ByVal

Description

In VB6, the Param Array keyword could be used to specify an arbitrary number of parameters for a declaration, sub, or function. ParamArrays were always passed by reference (ByRef). In Visual Basic .NET, ParamArrays are always passed by value; the Param Array keyword must be preceded by the ByVal keyword. When a parameter is passed by value, a copy of the variable is passed; when passed by reference, a pointer to the variable is passed. When passing by value, it is possible that the value of the variable may have changed and that the copy is out of date.

Recommendations

  • Review the code to make sure that the variables being passed as parameters are not being changed between the time that they are set and the time that they are used in a procedure.

  • If necessary, modify your code so that the procedure executes before the variables are modified.

VB6 Original Code

Private Sub Form_Load()
Dim myArray(0 To 2) As String
myArray(0) = "First"
myArray(1) = "Second"
myArray(2) = "Third"
Example myArray(0), myArray(1), myArray(2)
MsgBox myArray(1)
End Sub
Public Sub Example(ParamArray params() As Variant)
MsgBox "Second Parameter: " + params(1)
params(1) = params(1) + "_modified"
End Sub

C# Upgraded Code

private void Form_Load()
{
string[] myArray = new string[]{"", "", ""};
myArray[0] = "First";
myArray[1] = "Second";
myArray[2] = "Third";
Example(myArray[0], myArray[1], myArray[2]);
MessageBox.Show(myArray[1], AssemblyHelper.GetTitle(System.Reflection.Assembly.GetExecutingAssembly()));
}
//UPGRADE_WARNING: (1003) ParamArray params was changed from ByRef to ByVal.
public void Example(params object[] params_Renamed)
{
MessageBox.Show("Second Parameter: " + ReflectionHelper.GetPrimitiveValue<string>(params_Renamed[1]), AssemblyHelper.GetTitle(System.Reflection.Assembly.GetExecutingAssembly()));
params_Renamed[1] = ReflectionHelper.GetPrimitiveValue<string>(params_Renamed[1]) + "_modified";
}

VB.NET Upgraded Code

Private Sub Form_Load()
Dim myArray(2) As String
myArray(0) = "First"
myArray(1) = "Second"
myArray(2) = "Third"
Example(myArray(0), myArray(1), myArray(2))
MessageBox.Show(myArray(1), My.Application.Info.Title)
End Sub
'UPGRADE_WARNING: (1003) ParamArray params was changed from ByRef to ByVal.'
Public Sub Example(ParamArray ByVal params() As Object)
MessageBox.Show("Second Parameter: " & ReflectionHelper.GetPrimitiveValue(Of String)(params(1)), My.Application.Info.Title)
params(1) = ReflectionHelper.GetPrimitiveValue(Of String)(params(1)) & "_modified"
End Sub

1044 - Sub Main in a DLL won't get called

Description

This EWI is generated when a non-EXE project has a Sub Main as a startup object.

Recommendations

  • Since .NET DLLs do not have any method to execute it when the respective assembly is loaded, we have to add a static constructor and call the sub-main inside.

This will not be executed at the same time as VB6 but is the most similar behavior.

VB6 Original Code

Public Sub Main()
End Sub

C# Upgraded Code

public class Class1
{
//UPGRADE_WARNING: (1044) Sub Main in a DLL won't get called.
public void Main()
{
}
}

VB.NET Upgraded Code

Public Class Class1
'UPGRADE_WARNING: (1044) Sub Main in a DLL wont get called.'
Public Sub Main()
End Sub
End Class

1047 - Application will terminate when Sub Main() finishes

Description

Standard EXE applications written in VB6 can be started either from the Main sub or a startup form. The use of the Main sub is a very common practice in large applications, because it allows the execution of initialization logic before the load of the main form of the application, and allows separation of this logic from the logic of the form.

When an application that uses the Main sub as a startup object is migrated using the VBUC, equivalent code is generated in .NET. However, due to differences in the behavior of VB6 and the .NET Framework, the applications behave differently. Some manual changes are required to achieve functional equivalence in this case.

In VB6, all the code in the Main sub is executed and the method ends, but the application continues to run while a form is displayed. In .NET, on the other hand, the application will exit as soon as the Main method exits, any open form at that point will be closed automatically.

See also: How to prevent the application from exiting immediately after starting

VB6 Original Code

Public Sub Main()
Form1.Show
Dim x As Integer
x = 10
MsgBox x
End Sub

C# Upgraded Code

//UPGRADE_WARNING: (1047) Application will terminate when Sub Main() finishes.
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(Form1.DefInstance);
int x = 10;
MessageBox.Show(x.ToString(), AssemblyHelper.GetTitle(System.Reflection.Assembly.GetExecutingAssembly()));
}

VB.NET Upgraded Code

'UPGRADE_WARNING: (1047) Application will terminate when Sub Main() finishes.'
<STAThread> _
Public Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
Application.Run(Form1.DefInstance)
Dim x As Integer = 10
MessageBox.Show(CStr(x), My.Application.Info.Title)
End Sub

1049 - Use of Null/IsNull() detected

Description

In Visual Basic 6, the Null keyword indicates that a field contained no valid data and the IsNull function is used to test for Null. In addition, Visual Basic 6 support Null propagation when Null was used in an expression; the result of the expression would also be Null. This EWI indicates the use of the Null keyword or the IsNull function.

Recommendations

  • In Visual Basic .NET, the Null keyword is still a reserved word, but it has no syntactical value, and the IsNull function is no longer supported.

  • C# also reserves the null keyword, but it is not to be confused with Null in Visual Basic 6. Visual Basic's Null keyword indicated missing or invalid data in data field, which is the purpose of DbNull in the .Net Framework. C#'s version of null is closer to Visual Basic 6's Nothing keyword.

  • Also, Null propagation is no longer supported in the .NET framework. When upgrading Visual Basic 6 applications avoid null propagation.

  • During the upgrade, Null is converted to DBNull, and IsNull is converted to IsDBNull of which there are several variants: Visual Basic's IsDBNull function, the Convert.IsDBNull method, the DataTableReader.IsDBNull method, and the IDataRecord.IsDBNull method.

  • The behavior of DBNull is slightly different than that of Null. Null could be used in functions and assignments as a Variant data type, however DBNull is a class and thus cannot be used as a direct replacement for Visual Basic's Null. DBNull.Value, however, can be passed as a value type in methods or assignments.

  • Where Null was used with a Variant data type, the Variant is converted to Object during the upgrade; in these cases, depending on the context, it may be more appropriate to use the Nothing and the IsNothing function for VB.NET. For C# use the null keyword and compare it with null with the equality operators (== and !=).

VB6 Original Code

Private Function ResultTxt(var As Variant)
If IsNull(var) Then
ResultTxt = "Null"
ElseIf TypeName(var) = "Integer" Or TypeName(var) = "Double" Or TypeName(var) = "Long" Then
ResultTxt = CStr(var)
Else
ResultTxt = """" & var & """"
End If
End Function

C# Upgraded Code

private string ResultTxt(string var)
{
//UPGRADE_WARNING: (1049) Use of Null/IsNull() detected.
if (Convert.IsDBNull(var))
{
return "Null";
}
else if (var is int || var is double || var is int)
{
return var;
}
else
{
return "\"" + var + "\"";
}
}

VB.NET Upgraded Code

Private Function ResultTxt(ByVal var As String) As String
'UPGRADE_WARNING: (2081) TypeName has a new behavior.'
'UPGRADE_WARNING: (1049) Use of Null/IsNull() detected.'
If Convert.IsDBNull(var) Then
Return "Null"
ElseIf var.GetType().Name = "Integer" Or var.GetType().Name = "Double" Or var.GetType().Name = "Long" Then
Return var
Else
Return """" & var & """"
End If
End Function

1063 - Arrays in structure %1 may need to be initialized before they can be used

Description

In C#, fixed-size arrays in structures are not supported. Fixed-size arrays that are members of structures defined in COM interfaces will need to be initialized before they can be used.

Recommendations

  • Call the Initialize function of the structure, right after the variable declaration.

VB6 Original Code

Private Type Structure2
Size As Integer
Numbers() As String
End Type
Private Type Structure1
name As String
str2 As Structure2
End Type

C# Upgraded Code

[Serializable]
private struct Structure2
{
public short Size;
public string[] Numbers;
}
[Serializable]
private struct Structure1
{
public string name;
//UPGRADE_WARNING: (1063) Arrays in structure str2 may need to be initialized before they can be used.
public Structure2 str2;
public static Structure1 CreateInstance()
{
Structure1 result = new Structure1();
result.name = String.Empty;
return result;
}
}

1068 - %1 of type %2 is being forced to %3

Description

The VBUC typing engine is able to infer the correct data type of undefined or Variant-type variables, and assign them the correct data type. In some scenarios, a variant-type variable is upgraded to a scalar value; this type of inference is done over the variable usage and context.

Recommendations

  • There are no recommendations.

VB6 Original Code

Public Function Exists(col As Collection, Index As String) As Boolean
Dim o As Variant
On Error GoTo Error
o = col(Index)
Error:
MsgBox "Fail Test"
End Function

C# Upgraded Code

internal static bool Exists(OrderedDictionary col, string Index)
{
object o = null;
try
{
//UPGRADE_WARNING: (1068) col() of type Variant is being forced to Scalar.
o = ReflectionHelper.GetPrimitiveValue(col[Index]);
}
catch
{
}
MessageBox.Show("Fail Test", AssemblyHelper.GetTitle(System.Reflection.Assembly.GetExecutingAssembly()));
return false;
}

VB.NET Upgraded Code

Public Function Exists(ByVal col As OrderedDictionary, ByVal Index As String) As Boolean
Dim o As Object
Try
'UPGRADE_WARNING: (1068) col() of type Variant is being forced to Scalar.'
o = ReflectionHelper.GetPrimitiveValue(col(Index))
Catch
End Try
MessageBox.Show("Fail Test", My.Application.Info.Title)
End Function

1070 - Member access from a dynamic/late binding object will be resolved using the ReflectionHelper

Description

When the VBUC's mechanism for solving Late Binding cannot determine the type of a class, and the ReflectionHelper feature is enabled, the ReflectionHelper class will be used to attempt to correctly resolve the late binding instance at runtime.

Recommendations

  • The ReflectionHelper class should be able to determine most cases of Late Binding at runtime.

  • Manually modify the upgraded code to determine the correct base class.

VB6 Original Code

The original code uses a variant to allow any object to be passed, and have its Enabled property set to true. VB6's late binding will solve this.

Public Sub SetEnabled(ctrl As Variant)
ctrl.Enabled = True
End Sub

In a case such as this where the VBUC cannot determine a base type for all items passed to the method, it will use the ReflectionHelper to solve the late binding issue at runtime, as seen in the following example.

C# Upgraded Code

public void SetEnabled(object ctrl)
{
ReflectionHelper.LetMember(ctrl, "Enabled", true);
}

VB.NET Upgraded Code

Public Sub SetEnabled(ByVal ctrl As Object)
ReflectionHelper.LetMember(ctrl, "Enabled", True)
End Sub

2050 - %1 Event %2.%3 was not upgraded

Description

The VBUC is able to map VB6 library members to .NET equivalents. These equivalents are chosen to provide maximum functional equivalence on the target platform. In particular scenarios, some class properties and events may not have a direct equivalent in .NET or may not have been mapped in the current release of the VBUC. The event handler is declared in the target source code but there are no equivalent events to associate the handler to.

Recommendations

  • Use a more specialized/similar component to the original one, which provides the required events in the target platform.

  • Create an explicit invocation to the event handler method in the given circumstances to simulate the event triggering.

  • Associate the upgraded handler method to one or more .NET available events in case those events satisfy the code requirements and the original event behavior.

  • Refactor the code to apply a different solution. This strategy makes sense especially when the concepts in .NET are very different from the VB6 ones. In these cases it is likely that the way to implement a particular process should be done with a different approach.

VB6 Original Code

Sample taken from Drag & Drop Changes in Visual Basic .NET

Private Sub Text1_OLEDragOver(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single, State As Integer)
'Make sure that the format is text.'
If Data.GetFormat(vbCFText) Then
'If it is text, enable dropping for the second Textbox.'
Text1.OLEDropMode = vbOLEDropManual
End If
End Sub

C# Upgraded Code

//UPGRADE_WARNING: (2050) TextBox Event Text1.OLEDragOver was not upgraded.
private void Text1_OLEDragOver(DataObject Data, int Effect, int Button, int Shift, float X, float Y, int State)
{
//Make sure that the format is text.
//UPGRADE_ISSUE: (2064) ClipBoardConstants property ClipBoardConstants.vbCFText was not upgraded.
if (Data.GetFormat((short) UpgradeStubs.VBRUN_ClipBoardConstants.getvbCFText()))
{
//If it is text, enable dropping for the second Textbox.
//UPGRADE_ISSUE: (2070) Constant vbOLEDropManual was not upgraded.
//UPGRADE_ISSUE: (2064) TextBox property Text1.OLEDropMode was not upgraded.
Text1.setOLEDropMode(UpgradeStubs.VBRUN_OLEDropConstants.getvbOLEDropManual());
}
}

C# Expected Code

In this case the sample code deals with manually handling Drag & Drop operations in a Visual Basic 6 application. As is clearly stated in that MSDN article we need to write custom code for the drag & drop operation. In this case, we replace the OLEDragOver event which no longer exists in .Net, and replaces it with the DragEnter event. Within this event, we add our drag & drop logic.

private void Text1_DragEnter(object sender, DragEventArgs e)
{
//Make sure that the format is text.
if (e.Data.GetDataPresent(DataFormats.Text))
{
//Allow drop
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

VB.NET Upgraded Code

'UPGRADE_WARNING: (2050) TextBox Event Text1.OLEDragOver was not upgraded.'
Private Sub Text1_OLEDragOver(ByVal Data As DataObject, ByVal Effect As Integer, ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single, ByVal State As Integer)
'Make sure that the format is text.'
'UPGRADE_ISSUE: (2064) ClipBoardConstants property ClipBoardConstants.vbCFText was not upgraded.'
If Data.GetFormat(UpgradeSolution1Support.UpgradeStubs.VBRUN_ClipBoardConstants.getvbCFText()) Then
'If it is text, enable dropping for the second Textbox.'
'UPGRADE_ISSUE: (2070) Constant vbOLEDropManual was not upgraded.'
'UPGRADE_ISSUE: (2064) TextBox property Text1.OLEDropMode was not upgraded.'
Text1.setOLEDropMode(UpgradeSolution1Support.UpgradeStubs.VBRUN_OLEDropConstants.getvbOLEDropManual())
End If
End Sub

VB.NET Expected Code

In this case, the sample code deals with manually handling Drag & Drop operations in a Visual Basic 6 application. As is clearly stated in that MSDN article we need to write custom code for the drag & drop operation. In this case, we replace the OLEDragOver event which no longer exists in .Net, and replaces it with the DragEnter event. Within this event, we add our drag & drop logic.

Private Sub Text1_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles Text1.DragEnter
'Make sure that the format is text.'
If (e.Data.GetDataPresent(DataFormats.Text)) Then
'Allow Drop'
e.Effect = DragDropEffects.Copy
Else
' Do not allow drop'
e.Effect = DragDropEffects.None
End If
End Sub

2065 - %1 %2 %3.%4 has a new behavior

Description

During the upgrade process, some events can be mapped to .NET with minor behavior differences. If this EWI is displayed in a given project, the selected target event will not threaten the functional equivalence of the resulting code but can create small differences that may require some manual adjustments.

Recommendations

Each case will have to be evaluated individually, as the behavior differences may not be significant for every project. Some common properties with new behaviors include the following:

  • System.Windows.Forms.Control.Controls collection, which is a hierarchical list of NamingContainers and is usually navigated recursively. It's important to remember that Child controls will not be in the parent's Controls collection.

  • Likewise, System.Windows.Forms.ControlCollection.Count does not represent the same number in .NET as in VB6, as only direct descendants of the current control will be counted. Any child controls will be part of their respective parent's ControlCollection.

VB6 Original Code

Public Sub ShowPicture(ByVal myForm As Form)
MsgBox (myForm.Picture.Height)
End Sub

C# Upgraded Code

internal static void ShowPicture(Form1 myForm)
{
//UPGRADE_WARNING: (2065) Form property myForm.Picture has a new behavior.
MessageBox.Show(myForm.BackgroundImage.Height.ToString(), AssemblyHelper.GetTitle(System.Reflection.Assembly.GetExecutingAssembly()));
}

VB.NET Upgraded Code

Public Sub ShowPicture(ByVal myForm As Form1)
'UPGRADE_WARNING: (2065) Form property myForm.Picture has a new behavior.'
MessageBox.Show(CStr(myForm.BackgroundImage.Height), My.Application.Info.Title)
End Sub

2074 - %1 %2 %3.%4 was upgraded to %5.%6 which has a new behavior

Description

During the upgrade process, some class members and events can be mapped to a .NET equivalent with minor behavior differences. If this EWI is displayed in a given project, the selected source structure will not threaten the functional equivalence of the resulting code but can require some manual adjustments.

Recommendations

VB6 Original Code

Public Sub NewBehaviorParent(ByVal c As Control)
Dim b As Form
Set b = c.Parent
MsgBox c.Name & "'s parent is " & b.Name
End Sub

C# Upgraded Code

public void NewBehaviorParent(Label c)
{
//UPGRADE_WARNING: (2074) Control property c.Parent was upgraded to c.FindForm which has a new behavior.
Form b = c.FindForm();
MessageBox.Show(c.Name + "'s parent is " + b.Name, AssemblyHelper.GetTitle(System.Reflection.Assembly.GetExecutingAssembly()));
}

VB.NET Upgraded Code

Public Sub NewBehaviorParent(ByVal c As Label)
'UPGRADE_WARNING: (2074) Control property c.Parent was upgraded to c.FindForm which has a new behavior.'
Dim b As Form = c.FindForm()
MessageBox.Show(c.Name & "'s parent is " & b.Name, My.Application.Info.Title)
End Sub

2077 - Change the default 0 index in the Rows property with the correct one

Description

When upgrading an ADO Recordset object to native ADO.NET, the VBUC converts all the Recordset objects to System.Data.DataSet, however, there are major differences between these two classes. Cursors in ADO control record navigation in a Recordset; this way you are always pointing to a current row in the Recordset. This concept is not available in a DataSet object, which contains a collection of tables, and each table contains a collection of Rows and Columns, among other data. Since there is no current row concept in a DataSet object, when there are uses of a Recordset's current row, the VBUC then converts them to be the first row of the first table in the DataSet, and this EWI is generated.

In ADO, most of the time Recordset objects contain a single table retrieved from the Database. Therefore, the generated DataSets will only have one table in their Tables collections.

Recommendations

  • Review case by case to see if the first record of the DataTable object is actually the one intended to be used. If not, a change of logic might be required to achieve the functional equivalence between the original application and the upgraded one.

  • Also, turn on the ADODB-RDO feature in the VBUC (when available) to generate "Foreach" structures in places that match common recordset navigation patterns.

VB6 Original Code

Dim cn As Connection
Dim rs1 As Recordset
Dim cmd As Command
Public Sub Example()
Set cmd = New Command
With cmd
.ActiveConnection = cn
.CommandText = "Select * from Customers where CustomerID = 42"
.CommandType = adCmdText
End With
Set rs1 = cmd.Execute
If rs1.EOF = False Then
If rs1!Name <> "" Then Debug.Print rs1!Name
If rs1!Email <> "" Then Debug.Print rs1!Email
End If
rs1.Close
End Sub

C# Upgraded Code

static SqlConnection cn = null;
static DataSet rs1 = null;
static SqlCommand cmd = null;
internal static void Example()
{
cmd = new SqlCommand();
cmd.Connection = cn;
cmd.CommandText = "Select * from Customers where CustomerID = 42";
cmd.CommandType = CommandType.Text;
SqlDataAdapter adap = new SqlDataAdapter(cmd.CommandText, cmd.Connection);
rs1 = new DataSet("dsl");
adap.Fill(rs1);
if (rs1.Tables[0].Rows.Count != 0)
{
//UPGRADE_WARNING: (2077) Change the default 0 index in the Rows property with the correct one.
if (Convert.ToString(rs1.Tables[0].Rows[0]["Name"]) != "")
{
//UPGRADE_WARNING: (2077) Change the default 0 index in the Rows property with the correct one.
Debug.WriteLine(Convert.ToString(rs1.Tables[0].Rows[0]["Name"]));
}
//UPGRADE_WARNING: (2077) Change the default 0 index in the Rows property with the correct one.
if (Convert.ToString(rs1.Tables[0].Rows[0]["Email"]) != "")
{
//UPGRADE_WARNING: (2077) Change the default 0 index in the Rows property with the correct one.
Debug.WriteLine(Convert.ToString(rs1.Tables[0].Rows[0]["Email"]));
}
}
}

VB.NET Upgraded Code

Dim cn As SqlConnection
Dim rs1 As DataSet
Dim cmd As SqlCommand
Public Sub ExampleOne()
cmd = New SqlCommand()
With cmd
.Connection = cn
.CommandText = "Select * from Customers where CustomerID = 42"
.CommandType = CommandType.Text
End With
Dim adap As SqlDataAdapter = New SqlDataAdapter(cmd.CommandText, cmd.Connection)
rs1 = New DataSet("dsl")
adap.Fill(rs1)
If rs1.Tables(0).Rows.Count <> 0 Then
'UPGRADE_WARNING: (2077) Change the default 0 index in the Rows property with the correct one.'
If rs1.Tables(0).Rows(0)("Name") <> "" Then
'UPGRADE_WARNING: (2077) Change the default 0 index in the Rows property with the correct one.'
Debug.WriteLine(rs1.Tables(0).Rows(0)("Name"))
End If
'UPGRADE_WARNING: (2077) Change the default 0 index in the Rows property with the correct one.'
If rs1.Tables(0).Rows(0)("Email") <> "" Then
'UPGRADE_WARNING: (2077) Change the default 0 index in the Rows property with the correct one.'
Debug.WriteLine(rs1.Tables(0).Rows(0)("Email"))
End If
End If
End Sub

2080 - %1 was upgraded to %2 and has a new behavior

Description

The Visual Basic Upgrade Companion converts VB6 library items (types and members) to .NET equivalents whenever possible. For some VB6 elements, there are .NET constructs that work in a very similar way but may differ in their behavior depending on how they are used. The VBUC generates this EWI for these scenarios.

During the upgrade process, some class members can be mapped to .NET structures with minor behavior differences. If this EWI is displayed in a given project, the selected target structure will keep the functional equivalence of the resulting code but may end up having small differences in some cases that may require some manual fine-tuning, such as methods that are called in a different order or text that is displayed in a different font.

Recommendations

  • Evaluate whether the specific differences might be present on the specific code being upgraded. Sometimes these potential differences will not affect the application depending on how the original VB6 element was used.

  • Apply some modification to the upgraded source code which references the conflictive elements so that the differences are resolved.

  • Implement a new element in .NET that does not show the conflictive behavior differences. This might be done from scratch or by taking advantage of the existing .NET elements by using extension or wrapping approaches.

VB6 Original Code

Public Function Exists(col As Collection, Index As String) As Boolean
Dim o As Variant
On Error GoTo Error
MsgBox o
Error:
Exists = o <> Empty
End Function

C# Upgraded Code

internal static bool Exists(OrderedDictionary col, string Index)
{
object o = null;
try
{
MessageBox.Show(ReflectionHelper.GetPrimitiveValue<string>(o), AssemblyHelper.GetTitle(System.Reflection.Assembly.GetExecutingAssembly()));
}
catch
{
}
//UPGRADE_WARNING: (2080) IsEmpty was upgraded to a comparison and has a new behavior.
return !Object.Equals(o, null);
}

VB.NET Upgraded Code

Public Function Exists(ByVal col As OrderedDictionary, ByVal Index As String) As Boolean
Dim o As Object
Try
MessageBox.Show(ReflectionHelper.GetPrimitiveValue(Of String)(o), My.Application.Info.Title)
Catch
End Try
'UPGRADE_WARNING: (2080) IsEmpty was upgraded to a comparison and has a new behavior.'
Return Not Object.Equals(o, Nothing)
End Function

2081 - %1 has a new behavior

Description

The Visual Basic Upgrade Companion converts VB6 library items (types and members) to .NET equivalents whenever possible. For some VB6 elements, there are .NET constructs that work in a very similar way but may differ in their behavior depending on how they are used. The VBUC generates this EWI for these scenarios.

During the upgrade process, some class members can be mapped to .NET structures with minor behavior differences. If this EWI is displayed in a given project, the selected target structure will keep the functional equivalence of the resulting code but may end up having small differences in some cases that may require some manual adjustments, such as methods that are called in a different order or text that is displayed in a different font.

Recommendations

  • Evaluate whether the specific differences might be present on the specific code being upgraded. Sometimes these potential differences will not affect the application depending on how the original VB6 element was used.

  • Apply some modification to the upgraded source code which references the conflictive elements so that the differences are resolved.

  • Implement a new element in .NET that does not show the conflictive behavior differences. This might be done from scratch or by taking advantage of the existing .NET elements by using extension or wrapping approaches.

VB6 Original Code

Public Sub DifferentBehavior()
Dim x As String
x = "string data type"
MsgBox LenB(x)
End Sub

C# Upgraded Code

public void DifferentBehavior()
{
string x = "string data type";
//UPGRADE_WARNING: (2081) LenB has a new behavior.
MessageBox.Show(Encoding.Unicode.GetByteCount(x).ToString(), AssemblyHelper.GetTitle(System.Reflection.Assembly.GetExecutingAssembly()));
}

VB.NET Upgraded Code

Public Sub DifferentBehavior()
Dim x As String = "string data type"
'UPGRADE_WARNING: (2081) LenB has a new behavior.'
MessageBox.Show(CStr(Encoding.Unicode.GetByteCount(x)), My.Application.Info.Title)
End Sub

2099 - Return value for Dir has a different behavior

Description

In VB6 when using the FileSystem.Dir we can get the first file name that matches the pathname used, but when no more file names match, it returns an empty string (""), that way you could perform an operation to get all the elements inside the Windows folder.

VB6 Original Code

Dim Files As String
Dim elementcounter As Integer
Files = Dir("C:\Windows\*.*", 16)
Do While (Files <> "")
List1.AddItem Version
elementcounter = elementcounter + 1
lblCount.Caption = "Elements count: " + CStr(elementcounter)
Files = Dir
Loop

When upgrading the previous code you will get the following code:

C# Upgraded Code

int elementcounter = 0;
//UPGRADE_WARNING: (2099) Return value for Dir has a new behavior...
string Files = FileSystem.Dir("C:\\Windows\\*.*", FileAttribute.Directory);
while((Files != ""))
{
List1.AddItem(Version);
elementcounter++;
lblCount.Text = "Elements count: " + elementcounter.ToString();
//UPGRADE_WARNING: (2099) Return value for Dir has a new behavior...
Files = FileSystem.Dir();
};

If no more file names match, the FileSystem.Dir will return a null value instead of an empty string, causing a runtime exception when the previous code is executed. A manual change to fix this behavior could be to change the comparison between the Files variable and the empty string with the IsNullOrEmpty method.

while(!String.IsNullOrEmpty(Files))
{
...
}

6002 - UserControl Event 1% is not supported

Description

Using the VBUC, ActiveX controls can be upgraded to .NET Windows Controls. This can present a problem when upgrading certain ActiveX User Controls that implement Events that are no longer supported in .NET. In most cases, this is because they do not have an exact equivalent in .NET.

Recommendations

Visual Basic 6.0 Support

Resources for those unfamiliar with Visual Basic 6.0 ActiveX Controls:

.NET Framework Support

VB6 Original Code

Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
Debug.Print "WriteProperties"
PropBag.WriteProperty "Caption", Caption, Extender.Name
End Sub

C# Upgraded Code

//UPGRADE_ISSUE: (2068) PropertyBag object was not upgraded.
//UPGRADE_WARNING: (6002) UserControl Event WriteProperties is not supported.
private void UserControl_WriteProperties(UpgradeStubs.PropertyBag PropBag)
{
Debug.WriteLine("WriteProperties");
//UPGRADE_ISSUE: (2064) UserControl property UserControl1.Extender was not upgraded.
//UPGRADE_ISSUE: (2064) PropertyBag method PropBag.WriteProperty was not upgraded.
PropBag.WriteProperty("Caption", Convert.ToString(Caption), this.getExtender().Name);
}

C# Expected Code

This solution makes use of Application Settings to replace Visual Basic 6's PropertyBag. In most cases, this is not necessary as .NET's design model maintains the state of properties without the use of the Settings object. This example assumes that these values need to be preserved between instances of the program. We've also fixed up the code relating to the use of the Extender object.

private void UserControl_WriteProperties()
{
Debug.WriteLine("WriteProperties");
Properties.Settings.Default.Caption = this.Name;
}

VB.NET Upgraded Code

'UPGRADE_ISSUE: (2068) PropertyBag object was not upgraded.'
'UPGRADE_WARNING: (6002) UserControl Event WriteProperties is not supported.'
Private Sub UserControl_WriteProperties(ByVal PropBag As UpgradeSolution1Support.UpgradeStubs.PropertyBag)
Debug.WriteLine("WriteProperties")
'UPGRADE_ISSUE: (2064) UserControl property UserControl1.Extender was not upgraded.'
'UPGRADE_ISSUE: (2064) PropertyBag method PropBag.WriteProperty was not upgraded.'
PropBag.WriteProperty("Caption", Text, Me.getExtender().Name)
End Sub

VB.NET Expected Code

This solution makes use of Application Settings to replace Visual Basic 6's PropertyBag. In most cases, this is not necessary as .NET's design model maintains the state of properties without the use of the Settings object. This example assumes that these values need to be preserved between instances of the program. We've also fixed up the code relating to the use of the Extender object.

Private Sub UserControl_WriteProperties()
Debug.WriteLine("WriteProperties")
My.Settings.Caption = Me.Name
End Sub

6021 - Casting 'int' to Enum may cause different behavior

Description

Enums in .NET are integral types and therefore any valid integral value can be cast to an Enum type. This is still possible even when the value being cast is outside of the values defined for the given enumeration. Thus, when mixing integers with Enums it is possible to create invalid Enumeration instances that do not reference any of the defined values for that Enumeration.

Recommendations

  • One possible workaround is to use Enum.IsDefined() method to verify if a given integral value is defined for a given Enumeration. This method however will not work for enumerations marked with the Flags attribute and which are used as a bit map.

  • Additionally, one can ensure logic checks explicitly for defined Enum values and does not assume Enum-typed variables will conform to the defined list of values.

  • In many cases, functional equivalence will be maintained, but an effort should be made to eliminate these casts to ensure a cleaner code.

VB6 Original Code

Public Enum HealthState
Alive
Dead
Unknown
End Enum
Public Sub Kill(ByRef health As HealthState, Optional ByVal command As Integer = 1)
health = command
End Sub

C# Upgraded Code

public enum HealthState
{
Alive,
Dead,
Unknown
}
internal static void Kill(ref HealthState health, int command = 1)
{
//UPGRADE_WARNING: (6021) Casting 'int' to Enum may cause different behaviour.
health = (HealthState) command;
}

VB.NET Upgraded Code

Public Enum HealthState
Alive
Dead
Unknown
End Enum
Public Sub Kill(ByRef health As HealthState, Optional ByVal command As Integer = 1)
health = command
End Sub

6022 - The CommonDialog CancelError property is not supported in .NET

Description

The property CommonDialog.CancelError is not currently supported by VBUC, nevertheless, it is possible to get a similar behavior or functionality by using .NET properties.

Recommendations

  • Understand how the CommonDialog.CancelError property is used in the VB6 source code and applies a manual change in order to emulate the behavior in the .NET platform. In .NET, the CommonDialog.ShowDialog() method shows the Dialog and after the dialog is closed, returns the information of the Dialog button that was pressed by the user. This returning value must be compared against the members of the System.Windows.Forms.DialogResult Enumeration.

VB6 Original Code

Private Sub cmdColour_Click()
On Error GoTo errhandler
CommonDialog1.CancelError = True
' Display the Colour dialog box'
CommonDialog1.ShowColor
' Set the forms background colour to the one selected'
Me.BackColor = CommonDialog1.Color
Exit Sub
errhandler:
MsgBox "You canceled the dialog box"
End Sub

C# Upgraded Code

private void cmdColour_Click()
{
try
{
//UPGRADE_ISSUE: (2064) MSComDlg.CommonDialog property CommonDialog1.CancelError was not upgraded.
CommonDialog1.setCancelError(true);
// Display the Colour dialog box
CommonDialog1Color.ShowDialog();
// Set the forms background colour to the one selected
this.BackColor = CommonDialog1Color.Color;
}
catch
{
MessageBox.Show("You canceled the dialog box", AssemblyHelper.GetTitle(System.Reflection.Assembly.GetExecutingAssembly()));
}
}

VB.NET Upgraded Code

Private Sub cmdColour_Click()
Try
'UPGRADE_ISSUE: (2064) MSComDlg.CommonDialog property CommonDialog1.CancelError was not upgraded.'
CommonDialog1.setCancelError(True)
' Display the Colour dialog box'
CommonDialog1Color.ShowDialog()
' Set the forms background colour to the one selected'
Me.BackColor = CommonDialog1Color.Color
Catch
MessageBox.Show("You canceled the dialog box", My.Application.Info.Title)
End Try
End Sub

7005 - Parameters (if any) must be set using the Arguments property of ProcessStartInfo

Description

This EWI appears when you are upgrading a Shell call, and it means that the way that parameters are passed to a process in .NET is different than the VB6's.

Recommendations

  • If you need to pass parameters to a command being executed by a Shell, split the command and the arguments.

VB6 Original Code

Public Sub ExecuteCmd(ByVal parameters As String)
res = Shell("command" + parameters, vbMaximizedFocus)
End Sub

C# Upgraded Code

public void ExecuteCmd(string parameters)
{
object res = null;
//UPGRADE_TODO: (7005) parameters (if any) must be set using the Arguments property of ProcessStartInfo.
ProcessStartInfo startInfo = new ProcessStartInfo("command" + parameters);
startInfo.WindowStyle = ProcessWindowStyle.Maximized;
res = Process.Start(startInfo).Id;
}

VB.NET Upgraded Code

Public Sub ExecuteCmd(ByVal parameters As String)
'UPGRADE_TODO: (7005) parameters (if any) must be set using the Arguments property of ProcessStartInfo.'
Dim startInfo As ProcessStartInfo = New ProcessStartInfo("command" & parameters)
startInfo.WindowStyle = ProcessWindowStyle.Maximized
Dim res As Double = Process.Start(startInfo).Id
End Sub

7006 - The Named argument %1 was not resolved and corresponds to the following expression %2

Description

Both VB6 and VB.Net have support for Named parameters, which allow parameters to be specified by name and in any order, regardless of the method signature. In C# versions 1 through 3.5 this feature is not supported. In most cases, VBUC can resolve this issue by matching named parameters to method signatures and ensuring parameters are sent in the right overload/order. However, in certain cases where the VBUC cannot resolve references to methods with named parameters, this EWI is emitted.

Recommendations

  • For C# migrations, the VBUC generally creates a series of method overloads that mimic the behavior of named parameters. This is normal behavior as it is the best approximation of optional named parameters in C#. Workarounds for this EWI include reordering parameters and using the Type.Missing reference for optional parameters that should be omitted. In cases where the reference could not be resolved, this is particularly important. Since this EWI is related to the VBUC not being able to find mapped references, ensuring that the machine doing the migration has access to all dependent assemblies and COM objects is essential. Repeating the migration once this has been correct can reduce or eliminate the occurrence of these EWIs.

Reference documentation for sample code:

Since this EWI only appears in C#, an example will be shown just for that language.

VB6 Source Code

Public Function LoginIntoMAPI(ByVal strProfile As String, ByVal strPassword As String) As MAPI.Session
On Error GoTo error_olemsg
'LoginIntoMAPI As MAPI.Session'
Set LoginIntoMAPI = CreateObject("MAPI.Session")
If Not LoginIntoMAPI Is Nothing Then
If strProfile <> "" And strPassword <> "" Then
'Known reference'
LoginIntoMAPI.Logon showDialog:=False, profileName:=strProfile, profilePassword:=strPassword, newSession:=False
'Unknown reference'
LoginIntoMAPI2.Logon showDialog:=False, profileName:=strProfile, profilePassword:=strPassword, newSession:=False
Else
Err.Raise 1273, "Cannot logon to email system: no profile name or password"
End If
End If
Exit Function
error_olemsg:
LoginIntoMAPI = Nothing
End Function

C# Upgraded Code

public MAPI.Session LoginIntoMAPI(string strProfile, string strPassword)
{
MAPI.Session result = null;
object LoginIntoMAPI2 = null;
try
{
//LoginIntoMAPI As MAPI.Session
result = new MAPI.Session();
if (result != null)
{
if (strProfile != "" && strPassword != "")
{
//Known reference
result.Logon(strProfile, strPassword, false, false, Type.Missing, Type.Missing, Type.Missing);
//Unknown reference
//UPGRADE_WARNING: (7006) The Named argument newSession was not resolved and corresponds to the following expression False.
//UPGRADE_WARNING: (7006) The Named argument profilePassword was not resolved and corresponds to the following expression strPassword.
//UPGRADE_WARNING: (7006) The Named argument profileName was not resolved and corresponds to the following expression strProfile.
//UPGRADE_WARNING: (7006) The Named argument showDialog was not resolved and corresponds to the following expression False.
//UPGRADE_TODO: (1067) Member Logon is not defined in type Variant.
LoginIntoMAPI2.Logon(false, strProfile, strPassword, false);
}
else
{
throw new System.Exception("1273, Cannot logon to email system: no profile name or password, ");
}
}
}
catch
{
return result;
}
}

C# Expected Code

public MAPI.Session LoginIntoMAPI(string strProfile, string strPassword)
{
MAPI.Session result = null;
object LoginIntoMAPI2 = null;
try
{
//LoginIntoMAPI As MAPI.Session
result = new MAPI.Session();
if (result != null)
{
if (strProfile != "" && strPassword != "")
{
//Known reference
result.Logon(strProfile, strPassword, false, false, Type.Missing, Type.Missing, Type.Missing);
//Unknown reference
LoginIntoMAPI2.Logon(false, strProfile, strPassword, false, Type.Missing, Type.Missing, Type.Missing);
}
else
{
throw new System.Exception("1273, Cannot logon to email system: no profile name or password, ");
}
}
}
catch
{
return result;
}
}

7008 - The ProgId could not be found on computer where this application was migrated

Description

This specific issue arises when converting VB6 applications that use API calls to interact with programs from the Microsoft Office suite, such as Word, Excel, or PowerPoint; the issue appears in the upgraded code if the application is converted on a machine that does not have the Microsoft Office components installed.

Recommendations

There are two different options to remove the issue:

  • Install the Microsoft Office components and then convert the application again using VBUC.

  • Solve this issue manually.

VB6 Original Code

Private Sub Form_Load()
Dim oWordApplication As Object
Set oWordApplication = GetObject(, "Word.Application")
If oWordApplication Is Nothing Then
Set oWordApplication = CreateObject("Word.Application")
End If
If oWordApplication Is Nothing Then
Err.Raise vbObjectError + 1, , " Word Application not installed on MVBS server "
End If
End Sub

C# Upgraded Code

private void Form_Load()
{
object oWordApplication = Interaction.GetObject(String.Empty, "Word.Application");
if (oWordApplication is null)
{
//UPGRADE_WARNING: (7008) The ProgId could not be found on computer where this application was migrated.
oWordApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Word.Application"));
}
if (oWordApplication is null)
{
throw new System.Exception((Constants.vbObjectError + 1).ToString() + ", , Word Application not installed on MVBS server ");
}
}

C# Expected Code

private void Form_Load()
{
Word.Application oWordApplication = (Word.Application) Interaction.GetObject(String.Empty, "Word.Application");
if (oWordApplication == null)
{
oWordApplication = new Word.Application();
}
if (oWordApplication == null)
{
throw new System.Exception((Constants.vbObjectError + 1).ToString() + ", Word Application not installed on MVBS server ");
}
}

VB.NET Upgraded Code

Private Sub Form_Load()
Dim oWordApplication As Object = System.Runtime.InteropServices.Marshal.GetActiveObject("Word.Application")
If oWordApplication Is Nothing Then
'UPGRADE_WARNING: (7008) The ProgId could not be found on computer where this application was migrated.'
oWordApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Word.Application"))
End If
If oWordApplication Is Nothing Then
Throw New System.Exception((Constants.vbObjectError + 1).ToString() + ", , Word Application not installed on MVBS server ")
End If
End Sub

VB.NET Expected Code

Private Sub Form_Load()
Dim oWordApplication As Word.Application = System.Runtime.InteropServices.Marshal.GetActiveObject("Word.Application")
If oWordApplication Is Nothing Then
oWordApplication = New Word.Application
End If
If oWordApplication Is Nothing Then
Throw New System.Exception((Constants.vbObjectError + 1).ToString() + ", " + + ", Word Application not installed on MVBS server ")
End If
End Sub

7009 - Multiples invocations to ShowDialog in Forms with ActiveX Controls might throw runtime exceptions

Description

This EWI is not caused by a compilation error but may cause a runtime exception. The objective of this warning is to advise you to verify that the application is not having runtime issues when the ShowDialog method is called constantly, on a form that contains .NET third-party components or controls used through COM Interop.

Recommendations

  • Test the runtime behavior of the form and make sure that no runtime exceptions are thrown when calling the ShowDialog method repeatedly.

8007 - Trying to marshal a non Blittable Type (%1). A special conversion might be required at this point. Moreover use 'External Marshalling attributes for Structs' feature enabled if required

Description

This message indicates that at least one of the arguments to an API call that has now been converted to a .NET Platform invoke call has an issue. One important thing to note is the concept of "Blittable Types". Most data types have a common representation in both managed and unmanaged memory and do not require special handling by the interop marshaler. These types are called blittable types because they do not require conversion when passed between managed and unmanaged code.

The following types from the System namespace are blittable types:

  • System.Byte

  • System.SByte

  • System.Int16

  • System.UInt16

  • System.Int32

  • System.UInt32

  • System.Int64

  • System.IntPtr

  • System.UIntPtr

The following complex types are also blittable types:

  • One-dimensional arrays of blittable types, such as an array of integers.

  • Formatted value types that contain only blittable types (and classes if they are marshaled as formatted types).

This is important for VB6 migrations because if you are calling a DLL you will not be able to pass a non-blittable type because that DLL will expect a binary representation different from that in the .NET virtual machine.

This is also an issue in other scenarios like: