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:
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:
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:
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.
VB Migration Partner employs several code analysis techniques to deliver high-quality
VB.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()
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()
x = 123
End Sub
generates the following VB.NET code
Public Sub Test()
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.
Also, notice that all implicitly-declared variables are Object variable. However,
users of VB Migration Partner Enterprise Edition can use an InferType
pragma to generate a less-generic data type. For example, the following VB6 code:
Public Sub Test()
x = 123
End Sub
Is converted to VB.NET as follows:
Public Sub Test()
Dim x As Integer
x = 123
End Sub
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()
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()
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:
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:
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)
Test2(n1, n2, n3)
End Sub
Public Sub Test2(ByVal n1 As Short, ByRef n2 As Short, ByRef n3 As Short)
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)
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
Here’s the result from VB Migration Partner:
Public Sub Test(ByVal n1 As Short, ByRef n2 As Short, ByVal n3 As Short)
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
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
This feature requires VB Migration Partner Enterprise edition
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:
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
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
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:
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:
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:
Dim x As Object = Nothing
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
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 = ""
You can also use a SetType pragma to replace the data type from inside the VB6 code:
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:
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.
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:
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:
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.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>"
Wrapping fields in properties
VB Migration Partner Enterprise Edition 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:
Public Name As String
Public Widget As New Widget
is rendered 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
The IsNot operator
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
This feature requires VB Migration Partner Enterprise edition
VB Migration Partner Enterprise Edition 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)
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 VB.NET
Sub Test(ByVal obj As Object, ByVal obj2 As Object)
If (obj IsNot Nothing) AndAlso (obj.Text = "") Then
Debug.WriteLine("Empty Text")
End If
If (obj IsNot Nothing) AndAlso (obj2 IsNot Nothing) AndAlso (obj.Text = obj2.Text) Then
Debug.WriteLine("Same text")
End If
End Sub
You can further simplify the expression by dropping the parenthesis around each
subexpressions, by using the following pragma:
Structured Exception Handling
This feature requires VB Migration Partner Enterprise edition
VB Migration Partner Enterprise Edition 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()
On Error Goto ErrorHandler
ErrorHandler:
End Sub
is translated to the following VB.NET code:
Sub Test()
Try
Catch _ex As Exception
End Try
End Sub
The Try…Catch block can be inserted only if the following conditions are
all true:
- The method contains only a single On Error GoTo <label> method.
- 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.
- The method doesn’t contain GoSub, Resume, or Resume Next statements. (Resume
<label> statements are acceptable, though)
- 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.)
- 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.
- 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()
On Error Goto ErrorHandler
If x > 0 Then GoTo ErrorHandler2
ErrorHandler:
ErrorHandler2:
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, 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
On Error Resume Next
Reciprocal = 1 / x
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
Catch
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:
are converted using a Try-Catch block.
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
This feature requires VB Migration Partner Enterprise edition
VB Migration Partner Enterprise Edition 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
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:
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:
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:
- The method doesn't contain any Resume statement. (On Error Resume Next is OK, though).
- The target label isn't located inside a If, Select, For, Do, or Loop block.
- The target label isn't referenced by a Goto, On Goto/Gosub, or On Error statement.
- The target label must be preceded by a Goto, Return, End, Or Exit Sub/Function/Property
statement.
- 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.)
- 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
This feature requires VB Migration Partner Enterprise edition
VB Migration Partner Enterprise Edition 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:
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
Return m_Width
End Get
Set(ByVal value As Object)
m_Width = value
End Set
End Property
Public Sub Test(ByRef x As Short, ByRef frm As Object)
Dim res As Object = Nothing
Dim v1 As Object = Nothing
Dim v2 As Object = Nothing
v1 = x * 10
v2 = Width + x
frm = New Form1()
res = frm.Caption
End Sub
Variant variables – either implicitly declared or not – are translated
into Object variables, which causes 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 Enterprise Edition 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:
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)
Dim res As Object = Nothing
Dim v1 As Object = Nothing
Dim v2 As Single
v1 = x * 10
v2 = Height + x
frm = New Form1()
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:
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)
Dim res As String = Nothing
Dim v1 As Integer
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:
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
This feature requires VB Migration Partner Enterprise edition
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
x = 456
y = 789
End Sub
Users of VB Migration Partner Enterprise Edition 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:
then the code generated by VB Migration Partner would be as follows:
Public Sub Test(ByRef x As Integer)
x = 123
Exit Sub
End Sub
Unused members removal
This feature requires VB Migration Partner Enterprise edition
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:
Public UnusedField As String
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 Enterprise Edition 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:
then the result would be as follows:
Public UnusedField As String
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:
then VB Migration Partner generates (remarked out) nonoptimized VB.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
This feature requires VB Migration Partner Enterprise edition
VB Migration Partner Enterprise Edition 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:
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>
</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:
<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 VB.NET identifier and
doesn’t check whether the new name is unique. It’s up to the developer
ensuring that the generated VB.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 VB.NET, 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 VB.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
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
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.
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:
<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
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:
If someCondition Then
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
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.)
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:
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:
- 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.)
- Create a managed wrapper around the original ActiveX control. VB Migration Partner’s
package includes the AxWrapperGen utility that automates this process.
- 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:
- 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:
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
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 VS2008 projects. You can generate VS2005 projects
by adding a /version option:
AxWrapperGen c:\windows\system32\mscal.ocx /version:2005
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.
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.)