Warnings

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

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.

  • You can manually write the missing code if the affected code is simple enough.

1003 - ParamArray %1 was changed from ByRef to ByVal

Description

In VB6, the Param Array keyword could 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 ensure that the variables being passed as parameters are not changed between their setting and their use in a procedure.

  • If necessary, modify your code to execute the procedure 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 simultaneously 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:

  • Serializing content to files.

  • Sending messages through messaging mechanisms like named-pipes or sockets.

What should I do with non-blittable types?

If your type is not blittable and it is a struct the VBUC can automatically add marshalling attributes that will help you.

If your type is a decimal, a DateTime, or a GUID you can use some helper instead. For example the helpers by Andrey Akinshing

So, for example, for Decimal you can use:

namespace BlittableStructs {
   public struct BlittableDecimal {
      private long longValue;
      public BlittableDecimal(decimal value) {
         longValue = decimal.ToOACurrency(value);
      }
      public decimal Value {
         get { return decimal.FromOACurrency(longValue); }
         set { longValue = decimal.ToOACurrency(value); }
      }
      public static explicit operator BlittableDecimal(decimal value) {
         return new BlittableDecimal { Value = value };
      }
      public static implicit operator decimal (BlittableDecimal value) {
         return value.Value;
      }
   }
}

Or for DateTime you can use:

using System;
namespace BlittableStructs {
   public struct BlittableDateTime {
      private long ticks;
      public BlittableDateTime(DateTime dateTime) {
         ticks = dateTime.Ticks;
      }
      public DateTime Value {
         get { return new DateTime(ticks); }
         set { ticks = value.Ticks; }
      }
      public static explicit operator BlittableDateTime(DateTime value) {
         return new BlittableDateTime { Value = value };
      }
      public static implicit operator DateTime(BlittableDateTime value) {
         return value.Value;
      }
   }
}

Last updated