Previous | Index | Next 

4. Advanced Topics



4. Advanced Topics


4.1 The VBMigrationPartner_Support module

While VB Migration Partner can often automatically translate most VB6 constructs, in some cases it is necessary for you to either modify the VB6 code before the conversion or modify the .NET code after the conversion process.

In general, we recommend that you don’t alter the original VB6 code in a significant way if the VB6 application is still in production, because relevant edits should be thoroughly tested and validated. If you edit the original VB6 application you should be guaranteed that your edits don’t change the application’s behavior.

Ideally, you should modify the VB6 code only by means of pragmas. Pragmas are just remarks, therefore they can’t affect the original application in any way. For example, you can use the InsertStatement pragma to insert VB.NET statements in the middle of the code to be migrated:

    '## InsertStatement  If x < 0 Then Return x

The main limitation of the InsertStatement pragma is that it can only insert entire VB.NET statements; it can’t, for example, add function calls in the middle of an expression.

VB Migration Partner comes with a special VB6 module – stored in the VBMigrationPartner_Support.bas file – that contains many special methods that you can use in your VB6 applications to prepare them for a smooth translation to .NET. You need to include this module in the VB6 project being converted, so that you can use these methods; VB Migration Partner never translates this module nor includes it in the migrated .NET project.

All the methods in this support module have names with a trailing “6” character, therefore chances of name clashing with other methods or variables in the original VB6 application are negligible. All these methods are just “do-nothing” or “pass-through” methods and don’t alter VB6 behavior. To see how these methods can be useful, consider the following VB6 code fragment:

    ' change fore and back color properties of an object, if possible
    Sub ChangeColor(ByVal obj As Object, ByVal foreColor As Long, ByVal backColor As Long)
        On Error Resume Next
        obj.ForeColor = foreColor
        obj.BackColor = backColor
    End Sub

The problem with this code is that the target object is accessed through late binding, therefore VB Migration Partner has no way to determine that the ForeColor and BackColor properties take a System.Drawing.Color object and that a conversion from the 32-bit integer is needed.

One way to solve this problem is by means of the FromOleColor6 method defined in the VBMigrationPartner_Support module. After adding the module to current VB6 project, change the original code as follows:

    ' change fore and back color properties of an object, if possible
    Sub ChangeColor(ByVal obj As Object, ByVal foreColor As Long, ByVal backColor As Long)
        On Error Resume Next
        obj.ForeColor = FromOleColor6(foreColor)
        obj.BackColor = FromOleColor6(backColor)
    End Sub

The FromOleColor6 method is a “pass-through” method: it takes a 32-bit integer and returns it to the caller, without modifying it in any way. This ensures that the VB6 code continues to work as before the edit. However, when the code is translated to .NET, the VBMigrationPartner_Support module is discarded and the converted .NET code now references the FromOleColor6 method defined in the language support library, which converts a 32-bit value to the corresponding System.Drawing.Color object. The bottom line: the converted .NET code works like the original application and no manual fix after the migration is needed.

The VBMigrationPartner_Support module contains several conversion methods. You can use them when VB Migration Partner isn’t able to detect the type used in an expression or in an assignment, for example when the value is held in a Variant variable:

FromOleColor6, ToOleColor6: FromOleColor6 convert a 32-bit integer into a .NET Color value, whereas ToOleColor6 converts a .NET Color value into the corresponding 32-bit integer.

RGB6, QBColor6: Similar to RGB and QBColor methods, except they return a .NET Color object.

DateToDouble6, DoubleToDate6: Explicitly convert a Date value to a Double value, and vice versa.

ByteArrayToString6, StringToByteArray6: Explicit convert a Byte array to a String value, and vice versa.

Another group of methods force VB Migration Partner to generate the correct code when you access a Font object in late-bound mode. For example, consider this method:

    Sub SetControlFont(ByVal frm As Form) 	
        Dim ctrl As Control 	
        For ctrl In frm.Controls 		
            ctrl.Font.Name = "Arial" 		
            ctrl.Font.Size = 12 		
            ctrl.Font.Bold = True 	
        Next 
    End Sub

Once again, the problem is that ctrl is an IDispatch variable: the Font property is accessed in late-bound mode, therefore VB Migration Partner can’t determine that it references a System.Drawing.Font object and can’t take the appropriate steps to account for the fact that .NET font objects are immutable.

The VBMigrationPartner_Support module includes the FontChangeName6, FontChangeSize6, FontChangeBold6, FontChangeItalic6, FontChangeStrikeout6, and FontChangeUnderline6 methods which, as their name suggest, allow you to work around the read-only nature of the corresponding property of the .NET Font object. Here’s how you can use these methods to prepare previous code snippet for a smooth translation:

    Sub SetControlFont(ByVal frm As Form) 	
        Dim ctrl As Control 	
        For ctrl In frm.Controls 		
            ChangeFontName6(ctrl.Font, "Arial") 		
            ChangeFontSize6(ctrl.Font, 12) 		
            ChangeFontBold6(ctrl.Font, True) 	
        Next 
    End Sub

(This is exactly the code that VB Migration Partner would generate if the control variable was accessed in early-bound mode.)

Read the remarks in the VBMigrationPartner_Support module for a complete list of supported methods, their purpose, and example usages.




4.2 Code analysis features

VB Migration Partner employs several code analysis techniques to deliver high-quality .NET code.

Implicitly declared local variables

VB Migration Partner emits an UPGRADE INFO remark for each local variable that wasn’t declared explicitly in the original project. For example, the following VB6 code:

    Public Sub Test()
        x = 123
    End Sub

generates the following VB.NET code

    Public Sub Test()
        ' UPGRADE_INFO (#05B1): The 'x' variable wasn't declared explicitly.
        x = 123
    End Sub

You can force VB Migration Partner to generate an explicit declaration for such local variables by means of the DeclareImplicitVariables pragma, therefore this VB6 code:

    Public Sub Test()
        '## DeclareImplicitVariables
        x = 123
    End Sub

generates the following VB.NET code

    Public Sub Test()
        ' UPGRADE_INFO (#05B1): The 'x' variable wasn't declared explicitly.
        Dim x As Object = Nothing
        x = 123
    End Sub

Notice that the upgrade remark is emitted even if the variable declaration is now present in the generated code, but you can get rid of this comment by means of a DisableMessage pragma.

NOTE: the DeclareImplicitVariables pragma is ignored when converting to C#, because VB Migration Partner always generate the declaration of undeclared local variables in this case.

Also, notice that all implicitly-declared variables are Object variable. However, users of VB Migration Partner can use an InferType pragma to generate a less-generic data type. For example, the following VB6 code:

    Public Sub Test()
        '## DeclareImplicitVariables '## InferType '## DisableMessage
          05B1
        x = 123
    End Sub

Is converted to VB.NET as follows:

    Public Sub Test()
        Dim x As Integer
        x = 123
    End Sub

Unused members

Unused classes, fields, and methods are tagged with a special UPGRADE_INFO comment, which encourages the developer to whether the member is actually unnecessary and possibly delete it from the original VB6 application.

    Public Sub Test() 	
        ' UPGRADE_INFO (#0501): 'Test' member isn't used anywhere in current application. 
        ' ... 
    End Sub

The code analyzer is smart enough to mark as unused those methods that invoke each other but don’t belong to any possible execution path. For example, suppose that method Test references Test2. In this case, VB Migration Partner emits a slightly different message:

    Public Sub Test2() 	
        ' UPGRADE_INFO (#0511): The 'Test' member is referenced only by members that 
          ' haven't found to be used in the current project/solution. 
          ' ...
          
    End Sub

Bear in mind that code analysis can account only for members that are referenced by means of a strongly-typed variable. If the member is referenced exclusively via a Variant, Object, or Control variable, the member is mistakenly considered to be “unused”. A similar problem occurs if the member is referenced only though a CallByName method. In such cases you can tell VB Migration Partner not to include the field, property, or method in the list of referenced members by means of the MarkAsReference pragma:

    '## Test.MarkAsReferenced 
    Public Sub Test() 	
        ' ... 
    End Sub

Another case when VB Migration Partner can mistakenly find “unused” members is when migrating an ActiveX DLL or ActiveX EXE project. If you are migrating such a project individually, most of the public properties and methods of its public classes and controls are never referenced elsewhere. Even if you migrate the project as part of a group that contains a client project, chances are that the client project doesn’t reference each and every public member of all public classes of the ActiveX DLL or EXE project.

To cope with this situation, VB Migration Partner supports the MarkPublicAsReferenced pragma, which automatically marks as referenced all the public members in current class. You can apply this pragma at the project level, too, in which case all public members of all public classes in the project are marked as referenced:

    '## project:MarkPublicAsReferenced

Because of late-bound references and accesses from external processes, VB Migration Partner doesn’t automatically delete unused members. However, we provide an extender that deletes unused Declare and Const statements, which are never accessed in late-bound mode or from external projects. You can enable this feature in the Extenders tab of the Tools-Options dialog box.

Parameters unnecessarily marked as ByRef

By default, VB6’s default passing mechanism is by-reference, and many developers mistakenly omitted the ByVal keyword for parameters that were meant to be passed by value. VB Migration Partner emits an UPGRADE_INFO remark for all parameters that are passed byreference but aren’t assigned in the method itself, nor are passed by-reference to methods that modify them. Let’s consider the following VB6 code:

    Public Sub Test(n1 As Integer, ByRef n2 As Integer, n3 As Integer)  	
        Test2 n1, n2, n3 
    End Sub 
    
    Public Sub Test2(ByVal n1 As Integer, ByRef n2 As Integer, n3 As Integer)  	
        Debug.Print n1 	
        Debug.Print n2 	
        Get #1, , n3 
    End Sub

Parameter n1 is never assigned in method Test and can’t be modified by Test2 because it is passed into a ByVal parameter. Parameter n2 is passed to a ByRef parameter of Test2 method, yet Test2 never modifies it. Finally, parameter n3 is not flagged is passed to Test2 method in a ByRef parameter and it is indeed modified inside that method. Here’s is the code that VB Migration Partner emits:

    Public Sub Test(ByRef n1 As Short, ByRef n2 As Short, ByRef n3 As Short) 
        ' UPGRADE_INFO (#0551): The 'n1' parameter is neither assigned in current method nor 
        ' is passed to methods that modify it. Consider changing its declaration using the ByVal keyword. 
        ' UPGRADE_INFO (#0551): The 'n2' parameter is neither assigned in current method nor 
        ' is passed to methods that modify it. Consider changing its declaration using the ByVal keyword. 
        Test2(n1, n2, n3) 
    End Sub 
    
    Public Sub Test2(ByVal n1 As Short, ByRef n2 As Short, ByRef n3 As Short) 
        ' UPGRADE_INFO (#0551): The 'n2' parameter is neither assigned
          in current method nor ' is passed to methods that modify it. Consider changing its
          declaration using the ' ByVal keyword. 
        Debug.WriteLine(n1) 
        Debug.WriteLine(n2) 
        FileGet6(1, n3) 
    End Sub 

In general, you should consider changing the passing semantics of parameters flagged with the special UPGRADE_INFO remark, because ByVal parameters are often faster than ByRef parameters. The only exception to this rule is structures, and for this reason VB Migration Partner never applies this remark to Structures.

Even more important, replacing ByRef parameters with ByVal parameters often avoids hard-to-find bugs in the .NET code.

To help you in applying the ByVal keyword where needed, VB Migration Partner supports the UseByVal pragma, which can be applied at the project, file, or method level. This pragma takes one argument, chosen among the following values:

Yes: VB Migration Partner applies the ByVal keyword with unassigned parameters that implicitly use by-reference semantics (i.e. have no explicit ByRef or ByVal keyword).
Force: VB Migration Partner applies the ByVal keyword with all unassigned parameters that use by-reference semantics, even if an explicit ByRef keyword is found.
No: VB Migration Partner never applies the ByVal keyword. (This is the default behavior, yet this option can be useful to override another pragma at a broader scope.)

Let’s see how this pragma can affect the Test and Test2 methods seen in previous example:

    Public Sub Test(n1 As Integer, ByRef n2 As Integer, n3 As Integer)  	
        '## UseByVal Yes 
        Test2 n1, n2, n3 
    End Sub 
    
    Public Sub Test2(ByVal n1 As Integer, ByRef n2 As Integer, n3 As Integer)  	
        '## UseByVal Force 
        Debug.Print n1 	
        Debug.Print n2 	
        Get #1, , n3 
    End Sub

Here’s the result from VB Migration Partner:

    Public Sub Test(ByVal n1 As Short, ByRef n2 As Short, ByVal n3 As Short) 	
        ' UPGRADE_INFO (#0551): The 'n2' parameter is neither assigned
          in current method nor ' is passed to methods that modify it. Consider changing its
          declaration using the ' ByVal keyword. 
        Test2(n1, n2, n3) 
    End Sub 
    
    Public Sub Test2(ByVal n1 As Short, ByVal n2 As Short, ByRef n3 As Short) 	
        Debug.WriteLine(n1) 	
        Debug.WriteLine(n2) 	
        FileGet6(1, n3) 
    End Sub

All parameters that were flagged by the special remark in previous example are now defined with the ByVal keyword (and therefore have no remark associated with them), except the n2 parameter of Test method, because it was defined with an explicit ByRef keyword and we didn’t use the “Force” option in that method.

Unreachable code

Sections of unreachable code inside methods are tagged with a special UPGRADE_INFO comment; examples of such sections are those that immediately follow Exit Sub, Exit Function, and Exit Property statements:

    Public Sub Test() 
        ' ... 
        Exit Sub 
        ' UPGRADE_INFO (#0521): Unreachable code detected 
        Test2() 
    End Sub

VB Migration Partner correctly recognizes as unreachable code the region that follows a label that isn’t referenced by any GoTo, GoSub, On GoTo, On GoSub, On Error Goto, or Resume statement.

Unreachable code detection in current version of VB Migration Partner accounts neither for conditional structures (e.g. If blocks) nor for unhandled errors caused by an Error or Err.Raise statement. For example, in both these examples the code analyzer fails to detect that the call to Test3 is unreachable:

    Public Sub Test(ByVal x As Integer) 
        If x < 0 Then 
            ' ... 
            Exit Sub 
        Else 
            ' ... 
            Exit Sub 
        End If 
        
        Test3 
    End Sub
    
    Public Sub Test2(ByVal x As Integer)
        ' ... 
        Err.Raise 5
            
        Test3
    End Sub

The reason why VB Migration Partner doesn’t recognize as unreachable code the regions that are preceded by an Error or Err.Raise statement is that such error-related statements are ignored if an On Error Resume Next statement is active. You can have VB Migration Partner recognize those regions as unreachable by simply appending an Exit Sub (or Exit Function or Exit Property) immediately after the Error or Err.Raise statement.

Unneeded Class_Initialize events

The only way to initialize class fields under VB6 is to assign them in the Class_Initialize event handlers. (This method is named Form_Initialize inside forms, UserControl_Initialize inside user controls.)

    Public StartTime As Date
    Public NumberOfDays As Integer

    Private Sub Class_Initialize()
	    StartTime = Now
	    NumberOfDays = 10
    End Sub

VB Migration Partner attempts to move assignments from the Initialize event handler to the field declaration, thus the above VB6 code is translated to VB.NET as follows:

    ' UPGRADE_INFO (#0721): The 'Class_Initialize' method is empty
      because all ' its statements were rendered as class field initializers.
    Private Sub Class_Initialize_VB6()
    End Sub

You can tell VB Migration Partner not to generate empty Initialize methods by adding a project- or file-level RemoveUnusedMembers pragma in the original VB6 source code.

Unneeded Class_Terminate events

Many VB6 developers define a Class_Terminate event handler just to set class-level variables to Nothing. Such assignments are superfluous – because the object is released anyway when the “Me” object is destroyed, but this coding style doesn’t harm in VB6.

When you convert this code to VB.NET, however, the Class_Terminate handler is translated into a Finalize method. As you may know, finalizable .NET classes are less efficient than standard classes, therefore you should avoid to declare the Finalize method unless it is strictly necessary. To help you follow .NET Framework coding best practices, VB Migration Partner emits an UPDATE_INFO remark when a Class_Terminate event handler can be safely removed, as in this case:

    Private col As New Collection 	 
    
    ' UPGRADE_INFO (#0541): This 'Class_Terminate' method appears
      to be useless. Please ' delete it in the original project and restart migration.
    Private Sub Class_Terminate_VB6() 	
        col = Nothing 
    End Sub

Unneeded On Error Goto 0 statements

Many VB6 developers like to reset error handling on exiting a method, as in this code:

    Public Sub Test(n1 As Integer, ByRef n2 As Integer, n3 As Integer)
        On Error GoTo ErrorHandler
        Debug.Print n1, n2, 3
        
    ErrorHandler:
        ' ... ' reset error handling before exiting
        On Error GoTo 0
    End Sub

It turns out that the On Error GoTo 0 statement is useless in this case, because the VB error handling is always reset on exiting a method. For this reason, VB Migration Partner remarks out the statement:

    Public Sub Test(ByRef n1 As Short, ByRef n2 As Short, ByRef n3 As Short)
        On Error GoTo ErrorHandler 
        DebugPrintLine6(n1, TAB(), n2, TAB(), 3)
    ErrorHandler:
        ' ... ' IGNORED: On Error GoTo 0 
    End Sub

Members without “As” clause

Some VB6 developers – especially those who have written code in other languages, such as C or Pascal – incorrectly assume that the following statement defines three 32-bit variables:

    Dim x, y, z As Long

Instead, the statement defines two Variant variables and one Long variable. (More precisely, the type of x and y variables depends on the active DefXxx directive and is Variant only if no such a directive exist in current file.) VB Migration Partner translates the variable declarations correctly, but adds a warning so that you can spot the probable mistake:

    ' VB.NET
    ' UPGRADE_INFO (#0561): The 'x' symbol was defined without an
      explicit "As" clause.
    Dim x As Object = Nothing
    ' UPGRADE_INFO (#0561): The 'y' symbol was defined without an
      explicit "As" clause.
    Dim y As Object = Nothing
    Dim z As Integer
    
    // C#
    // UPGRADE_INFO (#0561): The 'x' symbol was defined without
      an explicit "As" clause.
    object x = null;
    // UPGRADE_INFO (#0561): The 'y' symbol was defined without
      an explicit "As" clause.
    object y = null;
    int z = 0;
    

A similar warning is emitted for functions, properties, and parameters that lack an explicit “As” clause.

String concatenation inside loops

In general, .NET is slower than VB6 at concatenating strings. As a matter of fact, you should avoid string concatenations inside a time-critical loop, as in this case:

    Dim s As String
    Dim n As Integer
    For n = 1 To 10000
        s = s & Str(n) & ","
    Next

VB Migration Partner helps you optimize your code by marking string concatenations inside a For, While, or Do loop with a special migration warning. This is how the previous code is converted to VB.NET:

    Dim s As String = ""
    Dim n As Short
    For n = 1 To 10000
        ' UPGRADE_INFO (#0571): String concatenation inside a loop.
          ' Consider declaring the 's' variable as a StringBuilder6 object.
        s = s & Str6(n) & ","
    Next

You should scrutinize all upgrade infos #0571 and decide whether you should change the type of the string variable into System.Text.StringBuilder or the special StringBuilder6 type exposed by VB Migration Partner’s support library. The latter type is usually preferable because it minimizes the code edits that are necessary to deliver working code:

    Dim s As StringBuilder6 = ""
    ' (remainder of code as before)

You can also use a SetType pragma to replace the data type from inside the VB6 code:

    '## s.SetType StringBuilder6
    Dim s As String

Unused type libraries

VB6 projects often contain references to type libraries that aren’t directly used by the current project. For example, if you create a new VB6 project by selecting the “Data Project” or the “VB Enterprise Edition Controls” template, Visual Basic 6 creates a project that references a number of type libraries that you don’t actually use. VB Migration Partner detects all unreferenced type libraries and lists them at the top of one of the .NET source files:

    ' UPGRADE_INFO (#0571): The 'DDSLibrary' type library is never used in current project. 
    ' Consider deleting it from VB6 project references.

Notice that the unused reference is not removed automatically from the converted .NET project. It’s up to the developer to decide whether the type library is truly unnecessary and remove it manually from the original VB6 project. This manual fix ensures that the .NET project contains only the references that are strictly necessary and speeds up the conversion process.




4.3 Refactoring features

VB Migration Partner leverages code analysis techniques to refactor the generated .NET code and make it faster and more readable.

Return values

VB6’s Function and Property Get procedures return a value to their caller by assigning the value to a local variable named after the procedure itself, as in this code:

    Function GetData() As Integer
        …
        If x > 0 Then
            GetData = x
            Exit Function
        End If
        …
    End Function

VB Migration Partner is able to collapse the assignment and the Exit statement into a VB.NET Return statement if it’s safe to do so:

    ' VB.NET    
    Function GetData() As Short
        …
        If x > 0 Then
            Return x
        End If
        …
    End Function
    
    // C#
    public short GetData()
    {
        …
        if (x > 0 )
        {
		return x;
	    }
	    …
    }

This feature is quite sophisticated and works as expected even in more intricate cases, as in the following example:

    Function GetData() As Integer 	
        … 	
        If x > 0 Then 		
            If y > 0 Then 			
                GetData = y 		
            Else 			
                GetData = x 		
            End If  		
            Exit Function 	
        End If 	
        … 	
        GetData = 0
    End Function

which translates to:

    ' VB.NET
    Function GetData() As Short 	
        … 	
        If x > 0 Then 		
            If y > 0 Then 			
                Return y 		
            Else 			
                Return x 		
            End If  		
            Exit Function 	
        End If 	
        … 	
        Return 0 
    End Function
    
    // C#
    public short GetData()
    {
        …
        if (x > 0)
        {   
            if (y > 0)
                return y;
            else
                return x;
        }
        …
        return 0;
     }

Notice that the Exit Function keyword is left in code and must be removed manually.

Variable initialization

VB Migration Partner attempts to merge a local variable’s declaration with its initialization. For example, the following VB6 code:

    Dim d As Double, i As Integer, v As Long, o As Object
    d = 1.8
    i = 11
    …
    If d = 0 Then v = 111

is translated to .NET as

    ' VB.NET
    Dim d As Double = 1.8
    Dim i As Short = 11
    Dim v As Integer
    Dim o As Object = Nothing
    …
    If d = 0 Then v = 111
    
    // C#
    double d = 1.8;
    short i = 11;
    int v = 0;
    object o = null;
    …
    if (d == 0)
        v = 111;

In this example, the d and i variables can be safely declared and initialized in a single statement, whereas the v variable can’t. This feature is enabled only for strings and scalar variables; Object and other reference type variables are always explicitly initialized to Nothing to prevent the VB.NET compiler from complaining about uninitialized variables that might throw a NullReference exception.

VB Migration Partner takes a conservative approach and doesn’t merge a variable declaration with the first assignment to that variable if the code block between these two statements includes keywords that change execution flow, for example method calls or Select Case blocks, or if the value being assigned isn’t constant.

You can force variable initialization by adding a MergeInitialization pragma for that specific variable. Consider this code fragment:

    '## n1.MergeInitialization Force
    Dim n1 As Integer, n2 As Integer  
    n1 = 10 + GetValue() 
    n2 = 11 + n1

In this case the MergeInitialization Force pragma informs VB Migration Partner that the call to the GetValue method can be safely included in the variable initialization. The code generator merges the declaration and initialization of n1 variable, because of the MergeInitialization pragma, but not of n2 variable:

    Dim n1 As Short = 10 + GetValue() 
    Dim n2 As Short  
    n2 = 11 + n1

You can disable this optimization at the project, file, method, or variable level by means of the following pragma:

    '## MergeInitialization No

VB Migration Partner is also able to merge class field declarations and assignments found in the Class_Initialize (or Form_Initialize) methods. However, the MergeInitialization pragma has no effect on class fields.

Note: in a method that contains one or more Gosub statements that are converted to separate methods, because of a ConvertGosubs pragma, the variable initialization feature is disabled.

Compound assignment operators

VB Migration Partner is able to replace plain assignment symbols with compound assignment operators, such as += or /=. For example, the following code:

    sum = sum + 1 
    value = value \ 2 
    text = text & "abc" 
    Label1.Caption = Label1.Caption & "." 
    Label2 = Label2 + "<end>"

is translated to .NET as follows:

    ' VB.NET
    sum += 1 
    value \= 2 
    text &= "abc" 
    Label1.Caption &= "." 
    Label2.Caption &= "<end>"     ' notice that
      default property has been resolved
    
    // C#
    sum+;
    value /= 2;
    text += "abc";
    Label1.Caption += ".";
    Label2.Caption += "<end>"     // notice that
      default property has been resolved

Wrapping fields in properties

VB Migration Partner can automatically wrap a specific public field in a class (or all public fields in the current class or project) in an equivalent Property. This feature is enabled by means of the AutoProperty pragma. For example, the following VB6 code:

	
    '## AutoProperty

    Public Name As String
    Public Widget As New Widget

is rendered to VB.NET as follows:

    Public Property Name() as String
        Get
            Return Name_InnerField
        End Get
	    Set(ByVal value As String)
            Name_InnerField = value
        End Set
    End Property

    Private Name_InnerField As String = ""

    Public Property Widget() As Widget
        Get
            If Widget_InnerField Is Nothing Then Widget_InnerField = New Widget()
            Return Widget_InnerField
        End Get
        Set(ByVal value As Widget)
            Widget_InnerField = value
        End Set
    End Property

    Private Widget_InnerField As Widget

Notice that the AutoProperty pragma automatically enforces the auto-instancing (As New) semantics if possible, regardless of whether the variable is under the scope of an AutoNew pragma.

Appending to the Text property

When the original VB6 code appends a string to the Text property of a TextBox, MaskEdBox, RichTextBox, or WLText control, VB Migration Partner can translate this operation into a more efficient call to the AppendText method. This is a special case of the pattern described at previous point:

    Me.Text1.Text = Me.Text1.Text & "<end>" 
    Text2 = Text2 + "<end>"

becomes

    Me.Text1.AppendText("<end>")
    Text2.AppendText("<end>")

If … Else simplification

VB Migration partner is able to drop If blocks if the Then and the Else blocks contain the same code. Such blocks can be produced, for example, by a conversion of a VB6 property. A VB6 Variant propery can have both the Property Let and Property Set blocks, if the property can be assigned either a scalar or an object value, as in this example:

    Private m_Tag As Variant
          
    Property Get Tag() As Variant
        If IsObject(m_Tag) Then
            Set Tag = m_Tag
        Else
            Tag = m_Tag
        End If
    End Property
    
    Property Let Tag(ByVal newValue As Variant)
        m_Tag = newValue
    End Property
   
    Property Set Tag(ByVal newValue As Variant)
        Set m_Tag = newValue
    End Property

Converting this property literally delivers the following VB.NET code:

    Private m_Tag As Object
    
    Public Property Tag() As Object
        Get
            If IsObject6(m_Tag) Then
                Return m_Tag
            Else
                Return m_Tag
            End If
        End Get
        Set(ByVal newValue As Object)
            If IsObject6(newValue) Then
                m_Tag = newValue
            Else
                m_Tag = newValue
            End If
        End Set
    End Property

VB Migration Partner detects that the Then and the Else portions of the If blocks contain the same executable statements and can simplify them as follows:

    Private m_Tag As Object
    
    Public Property Tag() As Object
        Get
            Return m_Tag
        End Get
        Set(ByVal newValue As Object)
            m_Tag = newValue
        End Set
    End Property

A similar optimization is applied when converting to C#.

The IsNot operator

When converting to VB.NET, VB Migration Partner can automatically to adopt the IsNot operator in all expressions that test whether two objects are equal or if an object is equal to Nothing. For example, the following VB6 code:

    If Not obj Is Nothing And Not obj Is obj2 Then

Is translated as follows:

    If obj IsNot Nothing And obj IsNot obj2 Then

Nested If merging

VB Migration Partner can detect whether the original VB6 code contains two or more nested If blocks and automatically merge them into a single If condition that contains the AndAlso operator. The key to this refactoring feature is the MergeIfs pragma, which can have project-, file-, or method-scope. For example, consider the following VB6 code:

    Sub Test(ByVal obj As Object, ByVal obj2 As Object)
        '## MergeIfs True
        If Not obj Is Nothing Then
            If obj.Text = "" Then Debug.Print "Empty Text"
        End If
        
        If Not obj Is Nothing Then
            If Not obj2 Is Nothing Then
                If obj.Text = obj2.Text Then Debug.Print "Same text"
            End If
        End If
    End Sub

This is how VB Migration Partner translates it to .NET

    ' VB.NET
    Sub Test(ByVal obj As Object, ByVal obj2 As Object)
        ' UPGRADE_INFO (#0581): Two nested If...End If blocks have been
          merged.
        If (obj IsNot Nothing) AndAlso (obj.Text = "") Then
            Debug.WriteLine("Empty Text")
        End If
        
        ' UPGRADE_INFO (#0581): Three nested If...End If blocks have
          been merged.
        If (obj IsNot Nothing) AndAlso (obj2 IsNot Nothing) AndAlso (obj.Text = obj2.Text) Then
            Debug.WriteLine("Same text")
        End If
    End Sub
    
    // C#
    public void Test(object obj, object obj2)
    {
        // UPGRADE_INFO (#0581): Two nested If...End If blocks have
          been merged.
        if (obj != null && obj.Text == "")
        {
            VB6Project.DebugWriteLine("Empty Text");
        }
        
        // UPGRADE_INFO (#0581): Three nested If...End If blocks have
          been merged.
        if (obj != null && obj2 != null && obj.Text == obj2.Text) 
        {
            VB6Project.DebugWriteLine("Same text");
        }
    }

You can further simplify the expression by dropping the parenthesis around each subexpressions, by using the following pragma:

    '## MergeIfs True, False

Notice that this optimization is disabled if there are one or more special pragmas between the two IF statements, including InsertStatement, ReplaceStatement, PreInclude and PostInclude.

Structured Exception Handling

VB Migration Partner can replace old-styled On Error Goto statements into structured exception handling (SEH) based on the Try-Catch block. You can activate this feature by means of the UseTryCatch pragma, which can have project-, file-, and method-level scope. For example, the following VB6 code:

    Sub Test()
        '## UseTryCatch
        On Error Goto ErrorHandler
        ' method body
     ErrorHandler:
        ' error handler
     End Sub

is translated to the following VB.NET code:

         Sub Test()
            Try
                ' IGNORED: On Error Goto ErrorHandler ' method body
            Catch _ex As Exception
                ' IGNORED: ErrorHandler: ' error handler
            End Try
        End Sub

You don’t need to specify any pragma when converting to C#, because the UseTryCatch pragma is implicitly applied:

        public void Test()
       {
	     try
	     {
	        // IGNORED: On Error Goto ErrorHandler // method body
	     }
	     catch (Exception ex)
	     {
	        // IGNORED: ErrorHandler: // error handler code
	     }
       }

The Try…Catch block can be inserted only if the following conditions are all true:

  1. The method contains only a single On Error GoTo <label> method.
  2. The On Error GoTo <label> statement doesn’t appear inside a conditional block such as If, For, Select Case, and isn’t preceded by a GoTo statement.
  3. The method doesn’t contain GoSub, Resume, or Resume Next statements. (Resume <label> statements are acceptable, though)
  4. There is no Goto statement in the method body that points to a label that is defined in the error handler. (Notice that it is ok if a GoTo in the error handler points to a label defined in the method body.)
  5. If the method contains one or more On Error Goto 0 statements, such statements must immediately precede a statement that causes exiting from the current method, e.g. Exit Sub or End Function.
  6. If the current method is a Property Let procedure, there must not be a Property Set procedure for the same property. Likewise, If the current method is a Property Set procedure, there must not be a Property Let procedure for the same property.

For example, the following VB6 code:

    Sub Test()
        '## UseTryCatch
        On Error Goto ErrorHandler
        ' method body
        If x > 0 Then GoTo ErrorHandler2
    
    ErrorHandler:
        ' error handler
    ErrorHandler2:
        ' do something here
    End Sub

can't be converted to VB.NET using a Try-Catch block because a GoTo statement in the method body points to a label that is defined in the error handler.

By default, when converting to VB.NET VB Migration Partner inserts a Try-Catch also if the method contains the On Error Resume Next statement plus another executable statement. For example, the following VB6 code:

    Function Reciprocal(ByVal x As Double) As Double
        '## UseTryCatch True
        On Error Resume Next
        Reciprocal = 1 / x
        ' returns zero if an error occurs (e.g. X is zero)
    End Function

contains only two executable statements (including On Error Resume Next) and is converted to:

    Function Reciprocal(ByVal x As Double) As Double
        Try
            Return 1 / x
            ' returns zero if an error occurs (e.g. X is zero)
        Catch
            ' Do nothing if an error occurs
        End Try
    End Function

Methods with three or more executable statements can be converted using Try Catch if you specify a value higher than 2 in the second argument for the UseTryCatch pragma. For example, all methods with up to 5 executable statements under the scope of the following pragma:

    '## UseTryCatch True, 5

are converted using a Try-Catch block. (The ability to convert On Error Resume Next to Try-Catch bloks isn't supported when converting to C#.)

Notice that the UseTryCatch pragma can be used together with the AutoDispose Force pragma, in which case VB Migration Partner generates a complete Try-Catch-Finally block.

GoSub refactoring

VB Migration Partner is able, in many circumstances, to automatically refactor old-styled Gosub keywords into calls to a separate method. This feature can be applied at the project-, file-, or method-level by means of the ConvertGosubs pragma. For example, consider the following VB6 code:

    Function GetValue(x As Integer) As Integer
        '## ConvertGosubs True
        On Error Resume Next
        
        Dim s As String, name As String
        s = "ABC"
        name = "Code Architects"
        GoSub BuildResult
        Exit Function
    BuildResult:
        GetValue = x + Len(s) + Len(name)
        Return
    End Function

VB Migration Partner detects that the code that begins at the BuildResult label (a.k.a. the target label) can be safely refactored to a separate method, that receives four arguments. The result of the conversion is therefore:

    Public Function GetValue(ByRef x As Short) As Short
        Dim s As String
        Dim name As String
        On Error Resume Next
    	    
        s = "ABC"
        name = "Code Architects"
        Gosub_GetValue_BuildResult(x, GetValue, s, name)
        Exit Function
    End Function
    
    Private Sub Gosub_GetValue_BuildResult(ByRef x As Short, ByRef GetValue As Short, _
        ByRef s As String, ByRef name As String)
        On Error Resume Next
        GetValue = x + Len6(s) + Len6(name)
    End Sub

Notice that the external method contains an On Error Resume Next statement, because the original GetValue method also contains this statement.

All arguments to the new Gosub_GetValue_BuildResult method are passed by reference, so that the caller can receive any value that has been modified inside the method (as is the case with the GetValue parameter in previous example.) You can have VB Migration Partner optimize this passing mechanism and use ByVal if possible by passing True as the second argument to the ConvertGosubs pragma:

    '## ConvertGosubs True, True

If this optimization is used in the above code, the separate method is rendered as follows:

    Private Sub Gosub_GetValue_BuildResult(ByVal x As Short, ByRef GetValue As Short, _
        ByVal s As String, ByVal name As String)
        On Error Resume Next
        GetValue = x + Len6(s) + Len6(name)
    End Sub

If you don’t like the name that VB Migration Partner assigns to the automatically-generated method, you can easily change it by means of a PostProcess pragma:

    '## PostProcess "Gosub_GetValue_BuildResult", "AssignValueResult"

It is important to keep in mind that the conversion of a Gosub block of code into a separate method isn’t always possible. More precisely, VB Migration Partner can perform this conversions only if all the following conditions are met:

  1. The method doesn't contain any Resume statement. (On Error Resume Next is OK, though).
  2. The target label isn't located inside a If, Select, For, Do, or Loop block.
  3. The target label isn't referenced by a Goto, On Goto/Gosub, or On Error statement.
  4. The target label must be preceded by a Goto, Return, End, Or Exit Sub/Function/Property statement.
  5. The code block must terminate with an unconditional Return, or an End Sub/Function/Property statement. (Blocks that terminate with Exit Sub/Function/Property statements aren't converted.)
  6. The block of code between the target label and the closing Return/End statement doesn't contain another label.

If a method contains one or more labels that can’t be converted to a separate method, VB Migration Partner still manages to convert the remaining labels. In general, a label can be converted to a separate method if it neither references nor is referenced by a label that can’t be converted (for example, a label that is defined inside an If block or a label that doesn’t end with an unconditional Return statement.)

Note: in a method that contains one or more Gosub statements that are converted to separate methods, the variable initialization merging feature is disabled.

Type inference

VB Migration Partners is capable, in most cases, to correctly deduce a less generic data type for Variant local variables, parameters, class fields, functions and properties. This applies both to members that were explicitly declared as Variant and to members that lacked an explicit As clause. To see how this feature works, consider the following VB6 code:

    '## DeclareImplicitVariables True
    
    Private m_Width As Single
    
    Property Get Width ()
        Width = m_Width
    End Property
    
    Property Let Width (value)
        m_Width = value
    End Property
    
    Sub Test(x As Integer, frm)
        Dim v1 As Variant, v2
        v1 = x * 10
        v2 = Width + x
        Set frm = New frmMain
        res = frm.Caption
    End Sub

By default this code is converted to VB.NET as follows:

    Private m_Width As Single
    
    Public Property Width() As Object
        Get
            ' UPGRADE_WARNING (#0364): Unable to assign default member of
              symbol 'Width'. ' Consider using the SetDefaultMember6 helper method.
            Return m_Width
        End Get
        Set(ByVal value As Object)
            ' UPGRADE_WARNING (#0354): Unable to read default member of
              symbol 'value'. ' Consider using the GetDefaultMember6 helper method.
            m_Width = value
        End Set
    End Property
    
    Public Sub Test(ByRef x As Short, ByRef frm As Object)
        ' UPGRADE_INFO (#05B1): The 'res' variable wasn't declared explicitly.
        Dim res As Object = Nothing
        ' UPGRADE_INFO (#0561): The 'frm' symbol was defined without
          an explicit "As" clause.
        Dim v1 As Object = Nothing
        ' UPGRADE_INFO (#0561): The 'v2' symbol was defined without
          an explicit "As" clause.
        Dim v2 As Object = Nothing
        ' UPGRADE_WARNING (#0364): Unable to assign default member of
          symbol 'v1'. ' Consider using the SetDefaultMember6 helper method.
        v1 = x * 10
        ' UPGRADE_WARNING (#0364): Unable to assign default member of
          symbol 'v2'. ' Consider using the SetDefaultMember6 helper method.
        v2 = Width + x
        frm = New Form1()
        ' UPGRADE_WARNING (#0354): Unable to read default member of
          symbol 'frm.Caption'. ' Consider using the GetDefaultMember6 helper method. ' UPGRADE_WARNING
          (#0364): Unable to assign default member of symbol 'res'. ' Consider using the SetDefaultMember6
          helper method.
        res = frm.Caption
    End Sub

Variant variables – either implicitly declared or not – are translated into Object variables, which cause several warning to be emitted and, more important, adds an overhead at runtime. This overhead can be often avoided by declaring the variable of a more defined scalar type. You can have VB Migration Partner change the data type for a given member by means of the SetType pragma, but this operation requires a careful analysis of which values are assigned to the variable.

Users of VB Migration Partner can have this analysis performed automatically, by inserting an InferType pragma at the project-, file-, method- or variable-level. For example, let’s assume that the previous VB6 code contains the following pragma at the file level:

    '## InferType Yes 

The Yes argument in this pragma makes VB Migration Partner attempt to infer the type of al the implicitly-declared local variables and of all the local variables, class fields, functions and properties that lack an explicit As clause:

    Private m_Width As Single
    
    Public Property Width() As Single
        Get
            Return m_Width
        End Get
        Set(ByVal value As Single)
            m_Width = value
        End Set
    End Property
    
    Public Sub Test(ByRef x As Short, ByRef frm As Object)
        ' UPGRADE_INFO (#0561): The 'frm' symbol was defined without
          an explicit "As" clause. ' UPGRADE_INFO (#05B1): The 'res' variable wasn't declared
          explicitly.
        Dim res As Object = Nothing
        Dim v1 As Object = Nothing
        ' UPGRADE_INFO (#0561): The 'v2' symbol was defined without
          an explicit "As" clause.
        Dim v2 As Single
        ' UPGRADE_WARNING (#0364): Unable to assign default member of
          symbol 'v1'. ' Consider using the SetDefaultMember6 helper method.
        v1 = x * 10
        v2 = Height + x
        frm = New Form1()
        ' UPGRADE_WARNING (#0354): Unable to read default member of
          symbol 'frm.Caption'. ' Consider using the GetDefaultMember6 helper method. ' UPGRADE_WARNING
          (#0364): Unable to assign default member of symbol 'res'. ' Consider using the SetDefaultMember6
          helper method.
        res = frm.Caption
    End Sub

In this new version, VB Migration Partner can infer the type of the Width property from the type of the m_Width variable, which in turn permits to infer the type of the v2 local variable. VB Migration Partner attempts to infer neither the type of the v1 local variable (because it is explicitly declared As Variant) nor the type of the frm parameter (because by default method parameters aren’t under the scope of the InferType pragma).

You can extend type inference to members that were declared with an explicit As Variant by using the Force argument in the pragma. In addition, passing True in the second (optional) argument extends the scope to method parameters:

    '## InferType Force, True

The result of the conversion is now the following:

    Private m_Width As Single
    
    Public Property Width() As Single
        Get
            Return m_Width
        End Get
        Set(ByVal value As Single)
            m_Width = value
        End Set
    End Property
    
    Public Sub Test(ByRef x As Short, ByRef frm As Form1)
        ' UPGRADE_INFO (#0561): The 'frm' symbol was defined without
          an explicit "As" clause. ' UPGRADE_INFO (#05B1): The 'res' variable wasn't declared
          explicitly.
        Dim res As String = Nothing
        Dim v1 As Integer
        ' UPGRADE_INFO (#0561): The 'v2' symbol was defined without
          an explicit "As" clause.
        Dim v2 As Single
        v1 = x * 10
        v2 = Width + x
        frm = New Form1()
        res = frm.Caption
    End Sub

Notice that in this new version the type of frm parameter is inferred correctly, which in turn permits to infer the type of the res local variable.

Type inference works both with scalar types and object types. For example, consider the following VB6 code:

    '## InferType
    Public Sub SetAddress(ByVal address As String)
        Dim tb
        Set tb = Me.txtAddress    ' where txtAddress is a TextBox
        tb = address
    End Sub

VB Migration Partner can infer that the tb variable is of type TextBox and, consequently, it can correctly expand its default property:

    Public Sub SetAddress(ByVal address As String)
        Dim tb As VB6TextBox = Me.txtAddress    
        tb.Text = address
    End Sub

Extending the pragma scope to method parameters isn’t always a good idea, because the current version of VB Migration Partner infers the type of parameters by looking at assignments inside the method and doesn’t account for implicit assignments that result from method calls. For this reason, the type inferred during the migration process might not be correct in some cases.

Important note: Type inference isn’t an exact science and you should always double-check that the data type inferred by VB Migration Partner is correct. Even better, we recommend that you use the InferType pragma only during early migration attempts and then you later assign a specific type to members by editing the original VB6 code or by means of the SetType pragma.

Unreachable code removal

All versions of VB Migration Partner insert an upgrade comment at the beginning of an unreachable code region. Unreachable code is, for example, the code that follows a Return or Exit Sub statement:

    Public Sub Test(ByRef x As Integer, ByRef y As Integer)
        x = 123
        Exit Sub
        ' UPGRADE INFO (#0521): Unreachable code detected
        x = 456
        y = 789
    End Sub

Users of VB Migration Partner can use the RemoveUnrechableCode pragma to either remove or comment out all the statements in the unreachable code block. This pragma takes one argument which can be equal to Off (do nothing, the default behavior), On (remove all statements in the unreachable block), or Remarks (comment out all statements in the unreachable block). For example, assuming that the previous code snippet is under the scope of the following pragma:

    '## RemoveUnreachableCode Remarks

then the code generated by VB Migration Partner would be as follows:

    Public Sub Test(ByRef x As Integer)
        x = 123
        Exit Sub
        ' UPGRADE INFO (#06F1): Unreachable code removed ' EXCLUDED:
          x = 456 ' EXCLUDED: y = 789
    End Sub

Unused members removal

All versions of VB Migration Partner insert an upgrade comment just before unused fields, constants, variables, properties, and methods. The actual message depends on whether the member is never used in the application or it is referenced by a method that, in turn, appears to be unused. The following piece of generated VB.NET code shows both kinds of messages:

    ' UPGRADE_INFO (#0501): the 'UnusedField' member isn’t used
      anywhere in current application
    Public UnusedField As String
    ' UPGRADE_INFO (#0511): the 'UnusedConst' member is referenced
      only by members that ' haven’t found to be used in current application
    Public Const UnusedConst As String = "Foobar"

VB Migration Partner comes with an extender that automatically removes unused constants and Declare statements.

In addition, users of VB Migration Partner can use a RemoveUnusedMembers pragma to automatically remove (or remark out) all sorts of unused members. For example, if the previous code snippet were under the scope of the following pragma:

    '## RemoveUnusedMembers Remarks

then the result would be as follows:

    ' UPGRADE_INFO (#0701): the 'UnusedField' member has been removed
      because ' it isn’t used anywhere in current application
    Public UnusedField As String
    ' UPGRADE_INFO (#0711): the 'UnusedConst' member has been removed
      because ' it is referenced only by members that haven’t found to be used in current
      application
    Public Const UnusedConst As String = "Foobar"

Please notice that the RemoveUnusedMembers pragma used with the Remarks disables a few other refactoring features, for example the ConvertGosubs pragma. In other words, if the following pragmas are active:

    '## RemoveUnusedMembers Remarks
    '## ConvertGosubs
    '## UseTryCatch

then VB Migration Partner generates (remarked out) nonoptimized .NET code, where Gosub keywords are not rendered as separate methods and On Error statements are not converted into Try-Catch blocks.

Important note: the RemoveUnusedMembers pragma deletes or remarks all members that haven’t found to be referenced by any other member in the current project or other projects in the current solution. However, this mechanism can lead to unwanted removal if the member is part of a public class that is accessed by another project (not in the solution) or if the member is accessed via late-binding. You can use the MarkAsReferenced or MarkPublicAsReferenced pragmas to account for the first case.

However, there is no simple way to detect whether a member is accessed via late-binding. For this reason the RemoveUnusedMembers pragma supports a second safeMode parameter. If this parameter is True then the pragma affects only members that can’t be reached via late-binding, e.g. constants, Declare statements, and members with a scope other than public.

Renaming program members

VB Migration Partner supports enforcement of renaming guidelines by means of declarative rules that can be specified in an XML file. Thanks to this feature you can – for example – rename all push button controls so that their name begins with “btn” and possibly delete the “cmd” prefix that appeared in their VB6 name.

You enable the XML-based renaming engine by means of the ApplyRenameRules pragma. This pragma takes an optional argument that, if specified, must be equal to the path of the XML file that contains the renaming rules. If the argument is omitted, VB Migration Partner uses the RenameRules.xml file in its install folder. (It is recommended that you make a copy of this file and have the pragma point to the copy rather than the original file created by the setup procedure.) Here’s an example of this pragma:

    '## ApplyRenameRules c:\apps\myrules.xml

An important note: this pragma has an implicit project scope and affects all the projects in the project group being migrated. (This behavior is an exception to the usual scoping rules for pragmas.)

The RenameRules.xml file provided with VB Migration Partner includes all the common naming rules for controls and forms. Here’s an excerpt of the file:

    <?xml version="1.0" encoding="utf-8" ?>
    <NamingRules>
        <Symbol kind="Form">
            <Rule pattern="^frm" replace="" />
            <Rule pattern="(Form)?(?<num>\d*)$" replace="Form${num}" />
        </Symbol>

        <Symbol kind="Component" type="VB.CommandButton" >
            <Rule pattern="^(cmd)?" replace="btn" />
            <Rule pattern="Button$" replace="" changeCase="pascal" isFinal="true” />
        </Symbol>

        <!-- more controls are dealt with here -->
    </NamingRules>

The kind attribute is used first to determine the kind of symbol the rule applies to. Valid names are Form, Class, Module, Type, UserControl, Enum, Component, Method (includes subs and functions), Field, Property, Event, EnumItem (the members of an Enum declaration), Constant, and Variable (includes, local variables and parameters.)

If kind is equal to Component or Variable, you can optionally include a type attribute, that specifies the VB6 type of the member to which the rule applies. For example, the following rule renames all Integer variables named “i" into “index”

    <Symbol kind="Variable" type="Integer">
        <Rule pattern="^i$" replace="index" ignoreCase="false" />
    </Symbol>

Renaming rules are based on regular expressions. The pattern attribute is used to decide whether the rule applies to a given member, and the replace attribute specifies how the member should be renamed. The ignoreCase optional attribute should be false if you want the pattern to be applied in case-sensitive mode (by default comparisons are case-insensitive). The changeCase optional attribute allows you to change the case of the result and can be equal to “lower”, “upper”, “pascal” (first char is uppercase), or “camel” (first char is lowercase).

Being Visual Basic a case-insensitive language, in most cases the default behavior is fine. However, consider the following renaming requirement: many VB6 developers preferred to use “C” as a prefix for all class names, but this guideline has been deprecated in VB.NET. Here’s a rule that drops the leading “C”, but only if it is followed by another uppercase character:

    <Symbol kind="Class">
        <Rule pattern="^C(?=[A-Z])" replace="" ignoreCase="false" />
    </Symbol>

The changeCase attribute is useful to enforce naming rules that involve the case of identifiers. For example, a few developers like all-uppercase constant names; there a rule that enforce this guideline:

    <Symbol kind="Constant" >
        <Rule pattern="^.+$" replace="${0}" changeCase="upper" />
    </Symbol>

You can restrict a rule to one or more project items by means of the projectItem optional attribute. This attribute is considered as a regular expression applied to the name of the parent project item where the symbol is defined. (The project item name is in the form “projectname.itemname”. For example, let’s say that you want to drop any “str” prefix that appears in string variables defined inside the frmMain form and in Helpers module of the TestApp project:

    <Symbol kind="Variable" type="String" projectItem="^TestApp\.(frmForm|Helpers)$" >
        <Rule pattern="^str" replace="" />
    </Symbol>

It is essential that the projectItem regular expression accounts for the project name; if you want to apply the rename rule to any project – as it’s often the case – you must specify the .+ regular expression for the project name part. For example, the following rule changes the name of the ChangeID method in the Helpers module in all converted projects

    <Symbol kind="Method" projectItem="^.+\.Helpers$" >
        <Rule pattern="^ChangeID" replace="ChangeIdentifier" />
    </Symbol>

The projectItem attribute is especially useful if your VB6 adopted the naming convention according to which all form names begin with “frm”, all classes with “cls” and so forth. In such a case, in fact, you can easily restrict a rule by relying on these prefixes:

    <!-- convert x,y into xPoint and yPoint, but only inside
      classes -->
    <Symbol kind="Variable"  type="Double" projectItem="^cls" >
        <Rule pattern="^(x|y)$"  replace="${0}Point" />
    </Symbol>

By default, <Symbol> tags that have a non-empty projectItem attribute are evaluated before rules where this attribute is omitted. All the <Rule> tags inside each block are evaluated in order, from the first one to the last one. However, if a rule has the isFinal attribute set to “true” then all the rules that follow are ignored.

Important note: VB Migration Partner renaming engine doesn’t check whether the new name assigned to symbols is a valid .NET identifier and doesn’t check whether the new name is unique. It’s up to the developer ensuring that the generated .NET code doesn’t include invalid or duplicated identifier names. In practice you’ll notice this kind of errors as soon as you compile the generated .NET code, therefore we don’t consider it a serious issue.

When applying this pragma, however, you should be aware that there is margin for other issues that do not cause a compilation error and that may go unnoticed if you don’t perform accurate tests on the generated .NET application. Consider the following VB6 code:

    Const Name As String  = "Code Architects"

    Public Sub Test()
        Const strName As String = "NAME"
        Debug.Print strName & " = " & Name   ' displays "NAME = Code
          Architects"
    End Sub

Next, let’s assume that you apply a rename rule that prefixes all string constants with the “str” prefix, if the prefix is missing. This causes the first constant to be assigned the same name of the local constant. The neat effect is that the local constant shadows the first constant and that the method displays a different string:

    Const strName As String  = "Code Architects"
    
    Public Sub Test()
        Const strName As String = "NAME"
        Debug.Print strName & " = " & strName    ' displays "NAME =
          NAME"
    End Sub

This sort of issues are quite subtle because they don’t cause any compilation error. The current version of VB Migration Partner when a rename rule causes this problem, therefore it’s your responsibility ensuring that functional equivalence isn’t compromised by rename rules.




4.4 Extenders

Developers can write extenders to receive notifications from VB Migration Partner during the parse and code generation process. An extender class can access VB Migration Partner’s object model, read pragmas (and dynamically add new ones), edit the VB6 code before it is being parsed, modify the .NET code after it has been generated, and so forth.

The following extender class inserts a remark at the top of all VB.NET code files produced during the migration process:

    ' this attribute marks the current DLL as an extender
    <Assembly: CodeArchitects.VB6Library.VB6SupportLibrary(True)>            
                
    <VBMigrationExtender("My first extender", _ 	
        "A demo extender for CodeArchitect's VB Migration Partner")> _ 
    Public Class DemoExtender    
        Implements IVBMigrationExtender    
        
        Public Sub Connect(ByVal data As ExtenderData) _ 		
                Implements IVBMigrationExtender.Connect       
            ' do nothing at connect time
        End Sub    
        
        Sub Notify(ByVal data As ExtenderData, ByVal info As _ 		
                OperationInfo) Implements IVBMigrationExtender.Notify       
            If info.Operation = OperationType.ProjectItemConverted Then 		
                Dim remark As String = "Generated by VB Migration Partner" & vbCrLf 		
                info.ProjectItem.NetCodeText = _ 			
                    remark & info.ProjectItem.NetCodeText       	
            End If    
        End Sub 
    End Class

All pragmas are visible to extenders. Even better, an extender can programmatically create new instances of pragmas and associate them with code entities, as if they were embedded in the VB6 code being migrated. This feature enables developers to overcome that static nature of pragmas hard-coded in VB6 code. For example, the decision to treat a variable as an auto-instancing variable might be taken after checking how the variable is used, as in this code:

    ' (this code runs inside an extender DLL) 
    If someCondition Then 	
        ' treat the "cn" field as a true auto-instancing variable
        
        Dim cnSymb As VBSymbol = info.ProjectItem.Symbol.Symbols("cn") 	
        Dim pragma As New VBPragma("AutoNew True", Nothing) 	
        cnSymb.Pragmas.Add(pragma)  
    End If

For VB Migration Partner to recognize an extender DLL, developers need to deploy the DLL in VB Migration Partner’s main directory. The interactive user can then enable and disable extensions from the Extensions tab of the Tools-Options dialog box.

The sample extenders that are provided with VB Migration Partner perform quite generic task, for example removing unused Const and Declare statements or polishing the code of a VB6 user control class after the conversion to VB.NET.

However, the main purpose of extenders is to customize VB Migration Partner’s behavior and output for specific projects and coding styles. For example, a software company might decide to create a base class for all their forms and therefore all the forms in the VB.NET application should derive from this base class rather than CodeArchitects.VB6Library.VB6Form. Implementing an extender that changes the base class for all forms is quite simple:

    Sub Notify(ByVal data As ExtenderData, ByVal info As OperationInfo) _
            Implements IVBMigrationExtender.Notify    
        If info.Operation = OperationType.ProjectItemConverted AndAlso
            info.ProjectItem.Symbol.Kind = SymbolKind.Form Then       
            ' VB Migration Partner has completed the conversion of a form
            info.ProjectItem.NetDesignerText = info.ProjectItem.NetDesignerText.Replace( _
                "Inherits System.Windows.Forms.Form", "Inherits CompanyName.CompanyFormBase")    
        End If 
    End Sub

(Notice that the CompanyName.CompanyFormBase class must inherit from CodeArchitects.VB6Library.VB6Form for the converted application to work correctly.)




4.5 Support for 3rd-party ActiveX controls

VB Migration Partner supports all the controls included in the Visual Basic 6 package, with the only exception of the OLE and Repeater controls. When migrating a form that contains unrecognized controls, such controls are transformed into “placeholder” controls that stand out on the form because of their red background.

Once you have a .NET control that mimics or wraps the original ActiveX control, you just need to copy the .NET DLL inside VB Migration Partner’s main directory; no registration is required. The next time you run VB Migration Partner, the new control is recognized as if it were one of the VB6 built-in controls.
Alternatively, you can copy it to a folder and then use an AddLibraryPath pragma to have VB Migration Partner recognize all the DLLs in that folder:

    '## AddLibraryPath "c:\mydlls"

VB Migration Partner can handle additional ActiveX controls, in addition to those that are supported out of the box. The exact sequence of actions that is necessary to implement such support depends on the type of the control you want to support. Three options are available, listed here in order of increased difficulty:

  1. Use VB Migration Partner to migrate it to .NET. (This option requires that the control is authored in VB6 and you have its source code.)
  2. Create a managed wrapper around the original ActiveX control. VB Migration Partner’s package includes the AxWrapperGen utility that automates this process.
  3. Create a fully managed control that behaves like the original control.

For completeness’s sake, when comparing all the available solutions you should also consider a fourth solution strategy:

  1. Migrate the application as usual, then manually replace the “placeholder” control with a .NET control and then manually fix all the references to the control’s properties and methods.

If you have the VB6 source code of the ActiveX control it is recommended that you follow the a) strategy. If source code isn’t available, the choice between remaining strategies depends on a number of considerations:

  • A .NET project that uses wrappers for ActiveX controls can’t benefit from some advantages of the .NET Framework platform, for example XCOPY deployment and side-by-side execution of different versions of the same component.
  • The COM Interop mechanism isn’t perfect and we have noticed that a few complex controls – most notably, the DataGrid control – occasionally crashes when placed on a .NET form. You should carefully test all forms that host one or more ActiveX controls, either directly or wrapped by a .NET class.
  • If the ActiveX control is used massively by the project being migrated – or in other projects that you plan to migrate in the near future – the time required to create a fully managed control that behaves like the original control can pay off nicely. 
  • If the application being migrated sparingly uses the control – for example it appears only in one or two forms – running VB Migration Partner and then manually fixing all references is possibly the most cost effective strategy

The remainder of this section explains how to implement strategy a) and b).


Migrating ActiveX Controls authored in VB6

If the control or component is authored with VB6 and you have its source code, in most cases adding support for that control or component is a simple process: just run VB Migration Partner to migrate the ActiveX DLL or ActiveX Control project that contains the user control, going through the usual convert-test-fix cycle. When the migrated project finally works correctly, you might want to polish the .NET code.

For example, most VB6 user controls have been created by means of the ActiveX Control Interface Wizard, which generates tons of VB6 code whose only purpose is remedying the lack of inheritance in VB6. For example, assume that you are migrating a VB6 user control that works like an enhanced TextBox control. Such a control surely includes a Text property that does nothing but wrapping the property with same name of the inner TextBox control:

    Public Property Get Text() As String
        Text = Text1.Text
    End Property

    Public Property Let Text(ByVal New_Text As String)
        Text1.Text() = New_Text
        PropertyChanged "Text"
    End Property

This property is translated correctly to VB.NET, however it is unnecessary and could be dropped without any negative impact on the migrated project. The simplest way to fix this code is by means of a couple of OutputMode pragmas:

    '## OutputMode Off 
    Public Property Get Text() As String
        Text = Text1.Text 	
    End Property 	
    
    Public Property Let Text(ByVal New_Text As String)
        Text1.Text() = New_Text
        PropertyChanged "Text"
    End Property 	
    
    '## OutputMode On

Also, you can use PostProcess pragmas to delete special remarks added by the ActiveX Control Interface Wizard, as well as calls to the PropertyChanged method (which are useless under .NET).

Running the AxWrapperGen tool

AxWrapperGen is a tool that is part of the VB Migration Partner package. (You can find it in the main installation directory, which by default is C:\Program Files\Code Architects\VB Migration Partner.) It is a command-line utility, therefore you must open a command window and run AxWrapperGen from there.

AxWrapperGen’s main purpose is allowing VB Migration Partner to support compiled 3rd-party ActiveX controls. AxWrapperGen takes one mandatory argument, that is, the path of the .ocx file that contains the ActiveX control to be wrapped. For example, the following command creates the wrapper class for the Microsoft Calendar control (and of course assumes that such control is installed in the c:\windows\system32 folder):

    AxWrapperGen  c:\windows\system32\mscal.ocx

(Notice that we are using the MSCAL.OCX control only for illustration purposes, because VB Migration Partner already supports this control.) If the file name contains spaces, you must enclose the name inside double quotes. AxWrapperGen is able to convert multiple ActiveX controls in one shot

    AxWrapperGen  c:\windows\system32\mscal.ocx   "c:\windows\system32\threed32.ocx"

By default, AxWrapperGen generates a new project and solution named AxWrapper in current directory. You can change these default by means of the /out switch (to indicate an alternative output directory) and /project switch (to set the name of the new project and solution):

    AxWrapperGen  c:\windows\system32\mscal.ocx   /out:c:\myapp /project:NetCalendar

By default, AxWrapperGen generates VS2008 projects. You can generate VS2010 projects by adding a /version option:

    AxWrapperGen c:\windows\system32\mscal.ocx /version:2010
or you can generate VS2010 projects that target .NET Framework 4.0 with this command:
    AxWrapperGen c:\windows\system32\mscal.ocx /version:2010_40
    

AxWrapperGen runs the AxImp tool – which is part of the .NET Framework SDK – behind the scenes, to generate two DLLs that work as the RCW (Runtime Component Wrapper) for the selected control. For example, the mscal.ocx control generates the following two files: msacal.dll and axmsacal.dll. You can specify the following five options, which AxWrapperGen passes to AxImp: /keyfile, /keycontainer, /publickey, /delaysign, and /source. For more information, read .NET Framework SDK documentation.

At the end of generation process, AxWrapperGen runs Microsoft Visual Studio and loads a solution that contains one .NET class library (DLL) project, containing a pair of classes for each ActiveX control:

  • The first class of each pair works as a wrapper for the original ActiveX control.
  • The second class of each pair provides support during the migration process.

In our example, the MSCAL.OCX file contains only one ActiveX control, therefore only two classes will be created: the class named VB6Calendar inherits from AxMSACAL.AxCalendar and wraps the ActiveX control; the second class is named VB6Calendar_Support And inherits from VB6ControlSupportBase. This class will be instantiated and used by VB Migration Partner during the migration process, but not at runtime during execution.

Visual Studio - Solution Explorer

Before compiling the solution, check the “Instructions” region at the top of each class. In most cases, the project that AxWrapperGen utility creates compiles correctly at the first attempt. The compilation process creates a DLL that you must manually copy into the VB Migration Partner’s main directory.

In some cases, however, you need to tweak the code that has been created. More precisely, if any property or method has to do with graphic units – in other words, it represents a width or height and is therefore affected by the container’s ScaleMode property – you need to shadow the original member and create your own. For example, the DataGrid control exposes a property named RowHeight, which requires this treatment. This is the code that implements the required fix:

    <Localizable(True)> _
    <Category("Appearance")> _ 	
    Public Shadows Property RowHeight() As Single
        Get
            Return VB6Utils.FromPixelY(Me, CInt(MyBase.RowHeight), False, True)
        End Get
        
        Set(ByVal value As Single) 
            MyBase.RowHeight = VB6Utils.ToPixelY(Me, value, False, True)
        End Set
    End Property

The bulk of the work is done inside the FromPixelY and ToPixelY methods of the VB6Utils class, contained in the CodeArchitects.VBLibrary.dll. These methods check the current ScaleMode value and perform all the required conversions. (Of course, the class exposes also the FromPixelX and ToPixelX methods, for measures along the X axis.)

For more information about converting ActiveX controls, please read the following whitepapers:




4.6 Using the VBMP command-line tool

VB Migration Partner supports automated conversions by means of the VBMP.EXE command-line too. You can use this tool inside batch files and in MSBuild projects. Its syntax is quite simple:

    VBMP projectfile  options 

Where projectfile is the complete path of the VB6 project (or project group) file, and options is zero or more of the following:

/out:path specifies the parent directory of the folder that will be created for the .NET solution. This option corresponds to the path you can enter in the Save tab of the Tools-Options dialog box.)
/language:LL specifies the target language. LL can be either VB or C# (the default is VB).
/version:VVVV specifies the Visual Basic version of the code to be output. VVVV can be either 2005, 2008, 2010 or 2010_40. (The default is 2008). The 2010_40 settings generates projects that target .NET Framework 4.0, all other settings generate projects that target .NET Framework 2.0/3.5.
/maxissues:NN specifies the max number of conversions issues. If the conversion process generates more issues than this value, the VBMP utility exits with a non-zero errorlevel and doesn’t proceed with the compilation step even if the /compile option is defined. (If omitted, this value is equal to 10.)
/compile causes the converted .NET to be compiled.
/clearcache clears the typelib cache.
/quiet suppresses most messages.
/help or /h displays a brief explanation of each option.

For example, the following command converts the c:\vb6\widgets.vbg project and creates a VB2010 solution in the c:\vbnet\widgets folder, then compiles the result unless the conversion process generated more than 30 migration issues:

    VBMP c:\vb6\widgets.vbg  /out:c:\vbnet   /compile /maxissues:30 /version:2010

If the specified VB6 project can’t be converted, or the conversion was aborted because of a fatal error, or the conversion produced more issues than allowed, or if the compilation step produced one or more errors, then VBMP returns a nonzero errorlevel to the operating system.




4.7 The VB Project Dumper add-in

VB Migration Partner converts all the properties assigned at design-time and whose value is stored in the hidden portion of .frm forms. However, in very few cases, the information stored in these .frm is encoded in a format that we didn’t manage to decode.

For example, we haven’t found a way to decode how properties of the MSChart control are stored in the .frm form. To correctly migrate this control you should load the original project in the VB6 IDE and run the VBMP Project Dumper add-in.

The VBMP Project Dumper is a VB6 add-in that adds a new command to the Add-ins menu, which - when invoked - creates an XML file containing all the properties of all the controls in the project. You need to invoke the command just once, before attempting the migration process. (You need to invoke it again only if you modify one or more properties of an MSChart control in the VB6 project or if accidentally delete the XML file.)

If you migrate a form containing an MSChart control or another control that may require decoding – for example the DataTimePicker control – VB Migration Partner searches for this XML file and issues a migration note asking to run the add-in if the file can’t be found.

You install the VBMP Project Dumper by registering the ProjectDumperAddin.dll using the RegSvr32 utility:

   RegSvr32 ProjectDumperAddin.dll




4.8 Support for Dynamic Data Exchange (DDE)

VB Migration Partner supports DDE-related properties (LinkItem, LinkTopic, LinkMode, LinkTimeout), methods (LinkExecute, LinkPoke, LinkSend, LinkRequest) and events (LinkOpen, LinkClose, LinkExecute, LinkError, LinkNotify).

It’s essential to bear in mind that DDE support is implemented by simulating the VB6 behavior, but without actually using any native DDE feature offered by Windows. This detail has an important consequence: DDE communications only work between .NET applications that have been converted by VB Migration Partner and that use VB Migration Partner’s support library. If your original VB6 code uses DDE to communicate with Microsoft Excel or any other compiled DDE server application, it won’t be impossible to establish the communication.

Internally, VB Migration Partner implements DDE-related features by means of inter-process Windows messages. In current release all DDE members are supported except the LinkTimeout property and the LinkError event. More precisely, DDE communications in converted .NET apps never time out and never raise a LinkError event. These features are likely to be implemented in a future release.

When you migrate a VB6 form that works as a DDE server, the corresponding .NET form will react to all DDE requests in the form
         appname|formlinktopic
where appname is the App.Title property of the original VB6 project and formlinktopic is the value of the form’s LinkTopic property. You can change the former value by means of the VB6Config.DDEAppTitle.




4.9 Positional pragmas

In general, you can store pragmas inside a *.pragmas file only if the pragma has a project-level scope. Starting with version 1.50, VB Migration Partner the ability to store any pragma inside a separate *.pragmas file, including pragmas with file, method, or variable scope. Pragmas that are added in this way are known as “positional pragmas”.

To put it simply, a positional pragma is a pragma that VB Migration Partner can “virtually” insert in the middle of any of the source files the project consists of. In other words, instead of physically putting the pragma in a VB6 source file, you provide VB Migration Partner with the information about where it should be inserted. A positional pragma has one of the following syntax:

      filename > statement ==> pragma       filename
          > statement <== pragma       filename > statement
          == pragma 

where:

  • filename is the name of the file where the pragma should be inserted (must include the extension);
  • che > separator marks the end of the file name;
  • statement is a line of code inside the file, that VB Migration Partner can use as a reference point to understand where the pragma should be inserted; it can also be a fragment of an entire line, provided that there is only one line in the file that matches the fragment;
  • pragma is the pragma to be inserted;
  • the ==> separator states that the pragma must be inserter after the reference line;
  • the <== separator states that the pragma must be inserted befor the reference line;
  • the == separator states that the pragma must replace the reference line.

For example, let’s say that you want to add the DefaultMemberSupport pragma as a positional pragma inside the DoSomething method of the MainForm.frm file. This is the text to include inside the VBMigrationPartner.pragmas file

       Mainform.frm > Sub DoSomething ==> '## DefaultMemberSupport True
                   

Notice that the reference text isn’t a complete line – method parameters are omitted, for example – yet it is surely an unambiguous way to point to the method in question. The ==> causes the DefaultMemberSupport pragma to be inserted after the method definition, therefore it affects the method itself.

Next, let’s say that you want to add a file-level UseByval pragma to the Widget.cls class. A minor problem in this case is that you have to find an unambiguous line of code that appears in the declaration section of the file. A very good candidate in most cases is the Option Explicit directive, thus the text in VBMigrationPartner.pragmas file should look like this:

       Widget.cls > Option Explicit <== '## UseByVal
                

You should notice that VB Migration Partner interprets these positional pragmas and actually inserts the pragma when the VB6 source file is being read. This detail is important and has two consequences:

  • when you browse the VB6 source code inside the VB Migration Partner’s editor, you actually see the pragma that was added as a positional pragma, even though the pragma is never physically present in the source code file;
  • VB Migration Partner prevents you from saving a VB6 source code file that was affected by one or more positional pragma (else the “virtual” pragma would be saved as well)

Positional pragmas open up many new possibilities when planning a migration. Thanks to positional pragmas the team that takes care of the migration process never really have to interact with the team that maintains the VB6 source code, adds new features, or fixes bugs. For a successful migration that adheres to the convert-test-fix methodology, in fact, the migration team only needs to have access to the latest VB6 source files produced by the other team, and the result of the migration will always be in sync with the most recent VB6 release.

It is even possible that the two teams never know each other and in fact the positional pragma feature has been introduced to make it possible for a company to outsource the migration to another, distinct (and possibly offshore) company.

Finally, please notice that – in spite of the “positional pragma” nickname – this mechanism is capable to virtually insert any sort of VB6 code in the project being migrated. You can therefore use it to make minor adjustments to the VB6 codebase without actually touching it. For example, assume that you would like to “virtually” change a VB6 statement inside MainForm.frm from

       Me.Caption = "My VB6 app" 
               

to

       Me.Caption = "My VB.NET app" 
               

Here’s the text line in VBMigrationPartner.pragmas file that does the trick

       MainForm.frm > Me.Caption = "My VB6 app" == Me.Caption = "My VB.NET app"
               


4.10 Using environment variables in pragmas

Starting with version 1.50, it is possible to include references to environment variables inside a pragma, so that the variable is expanded when the pragma is interpreted. The syntax for inserting an environment variable inside a pragma is the following:

      {%variablename%}

Additionally, VB Migration Partner recognizes two special placeholders, named “Date” and “Time”, which are expanded into the current date and time, respectively.

Environment variable expansions has two major uses. First, it allows you to better document the migrated code, by adding the date/time and the author, as in this example:

      '## NOTE Code migrated by {%username%} on {%date%} at {%time%}

During migration the above code code expands to a pragma such as

      '## NOTE Code migrated by Joe Doe on 3/24/2012 at 12.34 pm Second, variable exansions opens up another interesting possibility when multiple developers are migrating the same set of VB6 source code files. For example, assume that Developer A wants to modify the output path of the migrated project so that it points to folder C:\BINS. This can be achieved by means of the following pragma:
      '## project:PostProcess "bin\\(?Debug|Release)\\",
            "c:\\BINS\\${mod}\\",  True, True 
 

Next, let’s assume that Developer B wants to modify the output path of the migrated project so that it points to C:\BINS2. Obviously, he should use a slightly different PostProcess pragma, but if the two developers share the same set of source files only one pragma can have the desired effect.

The solution is using environment variable expansions:

      '## project:PostProcess "bin\\(?Debug|Release)\\",
            "{%bindir%}${mod}\\",  True, True 
 

where the bindir environment variable should be set differently by the two developers. More precisely, Developer A should launch VB Migration Partner by means of a batch file that contains this statement:

      Set bindir = c:\\BINS\\

whereas Developer B must include this statement

      Set bindir = c:\\BINS2\\

 

Previous | Index | Next