VB Migration Partner

MANUAL

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 VB.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 VB.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 VB.NET.

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 VB.NET, the VBMigrationPartner_Support module is discarded and the converted VB.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 VB.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 VB.NET code.

Unused members

This feature requires VB Migration Partner Enterprise edition

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:AddPublicAsReferenced

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

This feature requires VB Migration Partner Enterprise edition

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 VB.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

This feature requires VB Migration Partner Enterprise edition

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

Unreachable code detection in current version of VB Migration Partner accounts neither for conditional structures (e.g. If blocks) nor for unhandled errors. For example, in the following example the code analyzer fails to detect that the call to Test2 is unreachable:

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

Unneeded Class_Terminate events

This feature requires VB Migration Partner Enterprise edition

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 VB.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:

    ' 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

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

String concatenation inside loops

In general, VB.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 VB.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 VB.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 VB.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 VB.NET to make 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:

    Function GetData() As Short
        …
        If x > 0 Then
            Return x
        End If
        …
    End Function

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:

    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

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 VB.NET as

    Dim d As Double = 1.8
    Dim i As Integer = 11
    Dim v As Long
    Dim o As Object = Nothing
    …
    If d = 0 Then 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

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.8 
    value = value \ 2 
    text = text & "abc" 
    Label1.Caption = Label1.Caption & "." 
    Label2 = Label2 + "<end>"

is translated to VB.NET as follows:

    sum += 1.8 
    value \= 2 
    text &= "abc" 
    Label1.Caption &= "." 
    Label2.Caption &= "<end>"     ' notice that default property has been resolved

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



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 VB.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:

    <VBMigratorExtender("My first extender", _ 	
        "A demo extender for CodeArchitect's VB Migration Partner")> _ 
    Public Class DemoExtender    
        Implements IVBMigratorExtender    
        
        Public Sub Connect(ByVal data As ExtenderData) _ 		
                Implements IVBMigratorExtender.Connect       
            ' do nothing at connect time
        End Sub    
        
        Sub Notify(ByVal data As ExtenderData, ByVal info As _ 		
                OperationInfo) Implements IVBMigratorExtender.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 IVBMigratorExtender.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.)

Note: The SDK library for building extenders will be released some time after the launch of VB Migration Partner.




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 VB.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 VB.NET 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). Note: Details about strategy c) will be made available when the release version is launched.


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 VB.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 VB.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 VS2005 projects. You can generate VS2008 projects by adding a /version option:

    AxWrapperGen c:\windows\system32\mscal.ocx /version:2008

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 VB.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.)




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 VB.NET solution. This option corresponds to the path you can enter in the Save tab of the Tools-Options dialog box.)
/version:VVVV specifies the Visual Basic version of the code to be output. VVVV can be either 2005 or 2008. (The default is 2005.)
/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 VB.NET to be compiled.
/quiet suppresses most messages.
/help or /h displays a brief explanation of each option.

For example, the following command compiles the c:\vb6\widgets.vbg project and creates a VB2008 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:2008

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.


Previous | Index | Next