While VB Migration Partner can often automatically translate most VB6 constructs,
in some cases it is necessary for you to either modify the VB6 code before the conversion
or modify the .NET code after the conversion process.
In general, we recommend that you don’t alter the original VB6 code in a significant
way if the VB6 application is still in production, because relevant edits should
be thoroughly tested and validated. If you edit the original VB6 application you
should be guaranteed that your edits don’t change the application’s
behavior.
Ideally, you should modify the VB6 code only by means of pragmas. Pragmas are just
remarks, therefore they can’t affect the original application in any way.
For example, you can use the InsertStatement pragma to insert VB.NET statements
in the middle of the code to be migrated:
The main limitation of the InsertStatement pragma is that it can only insert entire
VB.NET statements; it can’t, for example, add function calls in the middle
of an expression.
VB Migration Partner comes with a special VB6 module – stored in the VBMigrationPartner_Support.bas
file – that contains many special methods that you can use in your VB6 applications
to prepare them for a smooth translation to .NET. You need to include this module
in the VB6 project being converted, so that you can use these methods; VB Migration
Partner never translates this module nor includes it in the migrated .NET project.
All the methods in this support module have names with a trailing “6”
character, therefore chances of name clashing with other methods or variables in
the original VB6 application are negligible. All these methods are just “do-nothing”
or “pass-through” methods and don’t alter VB6 behavior. To see
how these methods can be useful, consider the following VB6 code fragment:
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 .NET, the VBMigrationPartner_Support module is discarded and the converted
.NET code now references the FromOleColor6 method defined in the language support
library, which converts a 32-bit value to the corresponding System.Drawing.Color
object. The bottom line: the converted .NET code works like the original application
and no manual fix after the migration is needed.
The VBMigrationPartner_Support module contains several conversion methods. You can
use them when VB Migration Partner isn’t able to detect the type used in an
expression or in an assignment, for example when the value is held in a Variant
variable:
FromOleColor6, ToOleColor6: FromOleColor6 convert a 32-bit integer
into a .NET Color value, whereas ToOleColor6 converts a .NET Color value into the
corresponding 32-bit integer.
RGB6, QBColor6: Similar to RGB and QBColor methods, except they
return a .NET Color object.
DateToDouble6, DoubleToDate6: Explicitly convert a Date value to
a Double value, and vice versa.
ByteArrayToString6, StringToByteArray6: Explicit convert a Byte
array to a String value, and vice versa.
Another group of methods force VB Migration Partner to generate the correct code
when you access a Font object in late-bound mode. For example, consider this method:
Sub SetControlFont(ByVal frm As Form)
Dim ctrl As Control
For ctrl In frm.Controls
ctrl.Font.Name = "Arial"
ctrl.Font.Size = 12
ctrl.Font.Bold = True
Next
End Sub
Once again, the problem is that ctrl is an IDispatch variable: the Font
property is accessed in late-bound mode, therefore VB Migration Partner can’t
determine that it references a System.Drawing.Font object and can’t take the
appropriate steps to account for the fact that .NET font objects are immutable.
The VBMigrationPartner_Support module includes the FontChangeName6, FontChangeSize6,
FontChangeBold6, FontChangeItalic6, FontChangeStrikeout6, and FontChangeUnderline6
methods which, as their name suggest, allow you to work around the read-only nature
of the corresponding property of the .NET Font object. Here’s how you can
use these methods to prepare previous code snippet for a smooth translation:
Sub SetControlFont(ByVal frm As Form)
Dim ctrl As Control
For ctrl In frm.Controls
ChangeFontName6(ctrl.Font, "Arial")
ChangeFontSize6(ctrl.Font, 12)
ChangeFontBold6(ctrl.Font, True)
Next
End Sub
(This is exactly the code that VB Migration Partner would generate if the control
variable was accessed in early-bound mode.)
Read the remarks in the VBMigrationPartner_Support module for a complete list of
supported methods, their purpose, and example usages.
VB Migration Partner employs several code analysis techniques to deliver high-quality
.NET code.
Implicitly declared local variables
VB Migration Partner emits an UPGRADE INFO remark for each local variable that wasn’t
declared explicitly in the original project. For example, the following VB6 code:
Public Sub Test()
x = 123
End Sub
generates the following VB.NET code
Public Sub Test()
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.
NOTE: the DeclareImplicitVariables pragma is ignored when converting to C#,
because VB Migration Partner always generate the declaration of undeclared local
variables in this case.
Also, notice that all implicitly-declared variables are Object variable. However,
users of VB Migration Partner can use an
InferType pragma to generate a less-generic data type. For example,
the following VB6 code:
Public Sub Test()
x = 123
End Sub
Is converted to VB.NET as follows:
Public Sub Test()
Dim x As Integer
x = 123
End Sub
Unused members
Unused classes, fields, and methods are tagged with a special UPGRADE_INFO comment,
which encourages the developer to whether the member is actually unnecessary and
possibly delete it from the original VB6 application.
Public Sub Test()
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
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 .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
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
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
Many VB6 developers define a Class_Terminate event handler just to set class-level
variables to Nothing. Such assignments are superfluous – because the object
is released anyway when the “Me” object is destroyed, but this coding
style doesn’t harm in VB6.
When you convert this code to VB.NET, however, the Class_Terminate handler is translated
into a Finalize method. As you may know, finalizable .NET classes are less efficient
than standard classes, therefore you should avoid to declare the Finalize method
unless it is strictly necessary. To help you follow .NET Framework coding best practices,
VB Migration Partner emits an UPDATE_INFO remark when a Class_Terminate event handler
can be safely removed, as in this case:
Private col As New Collection
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
object x = null;
object y = null;
int z = 0;
A similar warning is emitted for functions, properties, and parameters that lack
an explicit “As” clause.
String concatenation inside loops
In general, .NET is slower than VB6 at concatenating strings. As a matter of fact,
you should avoid string concatenations inside a time-critical loop, as in this case:
Dim s As String
Dim n As Integer
For n = 1 To 10000
s = s & Str(n) & ","
Next
VB Migration Partner helps you optimize your code by marking string concatenations
inside a For, While, or Do loop with a special migration warning. This is how the
previous code is converted to VB.NET:
Dim s As String = ""
Dim n As Short
For n = 1 To 10000
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 .NET source files:
Notice that the unused reference is not removed automatically from the
converted .NET project. It’s up to the developer to decide whether the type
library is truly unnecessary and remove it manually from the original VB6 project.
This manual fix ensures that the .NET project contains only the references that
are strictly necessary and speeds up the conversion process.
VB Migration Partner leverages code analysis techniques to refactor the generated
.NET code and make it faster and more readable.
Return values
VB6’s Function and Property Get procedures return a value to their caller
by assigning the value to a local variable named after the procedure itself, as
in this code:
Function GetData() As Integer
…
If x > 0 Then
GetData = x
Exit Function
End If
…
End Function
VB Migration Partner is able to collapse the assignment and the Exit statement into
a VB.NET Return statement if it’s safe to do so:
Function GetData() As Short
…
If x > 0 Then
Return x
End If
…
End Function
public short GetData()
{
…
if (x > 0 )
{
return x;
}
…
}
This feature is quite sophisticated and works as expected even in more intricate
cases, as in the following example:
Function GetData() As Integer
…
If x > 0 Then
If y > 0 Then
GetData = y
Else
GetData = x
End If
Exit Function
End If
…
GetData = 0
End Function
which translates to:
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
public short GetData()
{
…
if (x > 0)
{
if (y > 0)
return y;
else
return x;
}
…
return 0;
}
Notice that the Exit Function keyword is left in code and must be removed manually.
Variable initialization
VB Migration Partner attempts to merge a local variable’s declaration with
its initialization. For example, the following VB6 code:
Dim d As Double, i As Integer, v As Long, o As Object
d = 1.8
i = 11
…
If d = 0 Then v = 111
is translated to .NET as
Dim d As Double = 1.8
Dim i As Short = 11
Dim v As Integer
Dim o As Object = Nothing
…
If d = 0 Then v = 111
double d = 1.8;
short i = 11;
int v = 0;
object o = null;
…
if (d == 0)
v = 111;
In this example, the d and i variables can be safely declared
and initialized in a single statement, whereas the v variable can’t.
This feature is enabled only for strings and scalar variables; Object and other
reference type variables are always explicitly initialized to Nothing to prevent
the VB.NET compiler from complaining about uninitialized variables that might throw
a NullReference exception.
VB Migration Partner takes a conservative approach and doesn’t merge a variable
declaration with the first assignment to that variable if the code block between
these two statements includes keywords that change execution flow, for example method
calls or Select Case blocks, or if the value being assigned isn’t constant.
You can force variable initialization by adding a MergeInitialization pragma for
that specific variable. Consider this code fragment:
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
value = value \ 2
text = text & "abc"
Label1.Caption = Label1.Caption & "."
Label2 = Label2 + "<end>"
is translated to .NET as follows:
sum += 1
value \= 2
text &= "abc"
Label1.Caption &= "."
Label2.Caption &= "<end>"
sum+;
value /= 2;
text += "abc";
Label1.Caption += ".";
Label2.Caption += "<end>"
Wrapping fields in properties
VB Migration Partner can automatically wrap a specific public field in a class (or
all public fields in the current class or project) in an equivalent Property. This
feature is enabled by means of the AutoProperty pragma. For example, the following
VB6 code:
Public Name As String
Public Widget As New Widget
is rendered to VB.NET as follows:
Public Property Name() as String
Get
Return Name_InnerField
End Get
Set(ByVal value As String)
Name_InnerField = value
End Set
End Property
Private Name_InnerField As String = ""
Public Property Widget() As Widget
Get
If Widget_InnerField Is Nothing Then Widget_InnerField = New Widget()
Return Widget_InnerField
End Get
Set(ByVal value As Widget)
Widget_InnerField = value
End Set
End Property
Private Widget_InnerField As Widget
Notice that the AutoProperty pragma automatically enforces the auto-instancing (As
New) semantics if possible, regardless of whether the variable is under the scope
of an AutoNew pragma.
Appending to the Text property
When the original VB6 code appends a string to the Text property of a TextBox, MaskEdBox,
RichTextBox, or WLText control, VB Migration Partner can translate this operation
into a more efficient call to the AppendText method. This is a special case of the
pattern described at previous point:
Me.Text1.Text = Me.Text1.Text & "<end>"
Text2 = Text2 + "<end>"
becomes
Me.Text1.AppendText("<end>")
Text2.AppendText("<end>")
If … Else simplification
VB Migration partner is able to drop If blocks if the Then and the Else blocks contain
the same code. Such blocks can be produced, for example, by a conversion of a VB6
property. A VB6 Variant propery can have both the Property Let and Property Set
blocks, if the property can be assigned either a scalar or an object value, as in
this example:
Private m_Tag As Variant
Property Get Tag() As Variant
If IsObject(m_Tag) Then
Set Tag = m_Tag
Else
Tag = m_Tag
End If
End Property
Property Let Tag(ByVal newValue As Variant)
m_Tag = newValue
End Property
Property Set Tag(ByVal newValue As Variant)
Set m_Tag = newValue
End Property
Converting this property literally delivers the following VB.NET code:
Private m_Tag As Object
Public Property Tag() As Object
Get
If IsObject6(m_Tag) Then
Return m_Tag
Else
Return m_Tag
End If
End Get
Set(ByVal newValue As Object)
If IsObject6(newValue) Then
m_Tag = newValue
Else
m_Tag = newValue
End If
End Set
End Property
VB Migration Partner detects that the Then and the Else portions of the If blocks
contain the same executable statements and can simplify them as follows:
Private m_Tag As Object
Public Property Tag() As Object
Get
Return m_Tag
End Get
Set(ByVal newValue As Object)
m_Tag = newValue
End Set
End Property
A similar optimization is applied when converting to C#.
The IsNot operator
When converting to VB.NET, VB Migration Partner can automatically to adopt the IsNot
operator in all expressions that test whether two objects are equal or if an object
is equal to Nothing. For example, the following VB6 code:
If Not obj Is Nothing And Not obj Is obj2 Then
Is translated as follows:
If obj IsNot Nothing And obj IsNot obj2 Then
Nested If merging
VB Migration Partner can detect whether the original VB6 code contains two or more
nested If blocks and automatically merge them into a single If condition that contains
the AndAlso operator. The key to this refactoring feature is the MergeIfs
pragma, which can have project-, file-, or method-scope. For example, consider the
following VB6 code:
Sub Test(ByVal obj As Object, ByVal obj2 As Object)
If Not obj Is Nothing Then
If obj.Text = "" Then Debug.Print "Empty Text"
End If
If Not obj Is Nothing Then
If Not obj2 Is Nothing Then
If obj.Text = obj2.Text Then Debug.Print "Same text"
End If
End If
End Sub
This is how VB Migration Partner translates it to .NET
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
public void Test(object obj, object obj2)
{
if (obj != null && obj.Text == "")
{
VB6Project.DebugWriteLine("Empty Text");
}
if (obj != null && obj2 != null && obj.Text == obj2.Text)
{
VB6Project.DebugWriteLine("Same text");
}
}
You can further simplify the expression by dropping the parenthesis around each
subexpressions, by using the following pragma:
Notice that this optimization is disabled if there are one or more special pragmas
between the two IF statements, including InsertStatement, ReplaceStatement, PreInclude
and PostInclude.
Structured Exception Handling
VB Migration Partner can replace old-styled On Error Goto statements into structured
exception handling (SEH) based on the Try-Catch block. You can activate this feature
by means of the UseTryCatch pragma, which can have project-, file-, and method-level
scope. For example, the following VB6 code:
Sub Test()
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
You don’t need to specify any pragma when converting to C#, because the UseTryCatch
pragma is implicitly applied:
public void Test()
{
try
{
}
catch (Exception ex)
{
}
}
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, when converting to VB.NET VB Migration Partner inserts a Try-Catch also
if the method contains the On Error Resume Next statement plus another executable
statement. For example, the following VB6 code:
Function Reciprocal(ByVal x As Double) As Double
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. (The ability to convert On Error Resume Next
to Try-Catch bloks isn't supported when converting to C#.)
Notice that the UseTryCatch pragma can be used together with the AutoDispose Force
pragma, in which case VB Migration Partner generates a complete Try-Catch-Finally
block.
GoSub refactoring
VB Migration Partner is able, in many circumstances, to automatically refactor old-styled
Gosub keywords into calls to a separate method. This feature can be applied at the
project-, file-, or method-level by means of the ConvertGosubs
pragma. For example, consider the following VB6 code:
Function GetValue(x As Integer) As Integer
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
VB Migration Partners is capable, in most cases, to correctly deduce a less generic
data type for Variant local variables, parameters, class fields, functions and properties.
This applies both to members that were explicitly declared as Variant and to members
that lacked an explicit As clause. To see how this feature works, consider the following
VB6 code:
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 cause several warning to be emitted and, more important,
adds an overhead at runtime. This overhead can be often avoided by declaring the
variable of a more defined scalar type. You can have VB Migration Partner change
the data type for a given member by means of the SetType pragma,
but this operation requires a careful analysis of which values are assigned to the
variable.
Users of VB Migration Partner can have this analysis performed automatically, by
inserting an InferType pragma at the project-, file-, method- or
variable-level. For example, let’s assume that the previous VB6 code contains
the following pragma at the file level:
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
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 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
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 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 .NET code, where
Gosub keywords are not rendered as separate methods and On Error statements are
not converted into Try-Catch blocks.
Important note: the RemoveUnusedMembers pragma deletes or remarks
all members that haven’t found to be referenced by any other member in the
current project or other projects in the current solution. However, this mechanism
can lead to unwanted removal if the member is part of a public class that is accessed
by another project (not in the solution) or if the member is accessed via late-binding.
You can use the MarkAsReferenced or MarkPublicAsReferenced
pragmas to account for the first case.
However, there is no simple way to detect whether a member is accessed via late-binding.
For this reason the RemoveUnusedMembers pragma supports a second safeMode
parameter. If this parameter is True then the pragma affects only members that can’t
be reached via late-binding, e.g. constants, Declare statements, and members with
a scope other than public.
Renaming program members
VB Migration Partner supports enforcement of renaming guidelines by means of declarative
rules that can be specified in an XML file. Thanks to this feature you can –
for example – rename all push button controls so that their name begins with
“btn” and possibly delete the “cmd” prefix that appeared
in their VB6 name.
You enable the XML-based renaming engine by means of the ApplyRenameRules
pragma. This pragma takes an optional argument that, if specified, must be equal
to the path of the XML file that contains the renaming rules. If the argument is
omitted, VB Migration Partner uses the RenameRules.xml file in its install folder.
(It is recommended that you make a copy of this file and have the pragma point to
the copy rather than the original file created by the setup procedure.) Here’s
an example of this pragma:
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 .NET identifier and doesn’t
check whether the new name is unique. It’s up to the developer ensuring that
the generated .NET code doesn’t include invalid or duplicated identifier names.
In practice you’ll notice this kind of errors as soon as you compile the generated
.NET code, therefore we don’t consider it a serious issue.
When applying this pragma, however, you should be aware that there is margin for
other issues that do not cause a compilation error and that may go unnoticed
if you don’t perform accurate tests on the generated .NET application. Consider
the following VB6 code:
Const Name As String = "Code Architects"
Public Sub Test()
Const strName As String = "NAME"
Debug.Print strName & " = " & Name
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 .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:
<Assembly: CodeArchitects.VB6Library.VB6SupportLibrary(True)>
<VBMigrationExtender("My first extender", _
"A demo extender for CodeArchitect's VB Migration Partner")> _
Public Class DemoExtender
Implements IVBMigrationExtender
Public Sub Connect(ByVal data As ExtenderData) _
Implements IVBMigrationExtender.Connect
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 .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 .NET project that uses wrappers for ActiveX controls can’t benefit from
some advantages of the .NET Framework platform, for example XCOPY deployment and
side-by-side execution of different versions of the same component.
- The COM Interop mechanism isn’t perfect and we have noticed that a few complex
controls – most notably, the DataGrid control – occasionally crashes
when placed on a .NET form. You should carefully test all forms that host one or
more ActiveX controls, either directly or wrapped by a .NET class.
- If the ActiveX control is used massively by the project being migrated –
or in other projects that you plan to migrate in the near future – the time
required to create a fully managed control that behaves like the original control
can pay off nicely.
- If the application being migrated sparingly uses the control – for example
it appears only in one or two forms – running VB Migration Partner and then
manually fixing all references is possibly the most cost effective strategy
The remainder of this section explains how to implement strategy a) and b).
Migrating ActiveX Controls authored in VB6
If the control or component is authored with VB6 and you have its source code, in
most cases adding support for that control or component is a simple process: just
run VB Migration Partner to migrate the ActiveX DLL or ActiveX Control project that
contains the user control, going through the usual convert-test-fix cycle. When
the migrated project finally works correctly, you might want to polish the .NET
code.
For example, most VB6 user controls have been created by means of the ActiveX Control
Interface Wizard, which generates tons of VB6 code whose only purpose is remedying
the lack of inheritance in VB6. For example, assume that you are migrating a VB6
user control that works like an enhanced TextBox control. Such a control surely
includes a Text property that does nothing but wrapping the property with same name
of the inner TextBox control:
Public Property Get Text() As String
Text = Text1.Text
End Property
Public Property Let Text(ByVal New_Text As String)
Text1.Text() = New_Text
PropertyChanged "Text"
End Property
This property is translated correctly to VB.NET, however it is unnecessary and could
be dropped without any negative impact on the migrated project. The simplest way
to fix this code is by means of a couple of OutputMode pragmas:
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 .NET).
Running the AxWrapperGen tool
AxWrapperGen is a tool that is part of the VB Migration Partner package. (You can
find it in the main installation directory, which by default is C:\Program Files\Code
Architects\VB Migration Partner.) It is a command-line utility, therefore you must
open a command window and run AxWrapperGen from there.
AxWrapperGen’s main purpose is allowing VB Migration Partner to support compiled
3rd-party ActiveX controls. AxWrapperGen takes one mandatory argument, that is,
the path of the .ocx file that contains the ActiveX control to be wrapped. For example,
the following command creates the wrapper class for the Microsoft Calendar control
(and of course assumes that such control is installed in the c:\windows\system32
folder):
AxWrapperGen c:\windows\system32\mscal.ocx
(Notice that we are using the MSCAL.OCX control only for illustration purposes,
because VB Migration Partner already supports this control.) If the file name contains
spaces, you must enclose the name inside double quotes. AxWrapperGen is able to
convert multiple ActiveX controls in one shot
AxWrapperGen c:\windows\system32\mscal.ocx "c:\windows\system32\threed32.ocx"
By default, AxWrapperGen generates a new project and solution named AxWrapper
in current directory. You can change these default by means of the
/out switch (to indicate an alternative output directory) and /project
switch (to set the name of the new project and solution):
AxWrapperGen c:\windows\system32\mscal.ocx /out:c:\myapp /project:NetCalendar
By default, AxWrapperGen generates VS2008 projects. You can generate VS2010 projects
by adding a /version option:
AxWrapperGen c:\windows\system32\mscal.ocx /version:2010
or you can generate VS2010 projects that target .NET Framework 4.0 with this command:
AxWrapperGen c:\windows\system32\mscal.ocx /version:2010_40
AxWrapperGen runs the AxImp tool – which is part of the .NET Framework SDK
– behind the scenes, to generate two DLLs that work as the RCW (Runtime Component
Wrapper) for the selected control. For example, the mscal.ocx control generates
the following two files: msacal.dll and axmsacal.dll. You can specify the following
five options, which AxWrapperGen passes to AxImp: /keyfile, /keycontainer, /publickey,
/delaysign, and /source. For more information, read .NET Framework SDK documentation.
At the end of generation process, AxWrapperGen runs Microsoft Visual Studio and
loads a solution that contains one .NET class library (DLL) project, containing
a pair of classes for each ActiveX control:
- The first class of each pair works as a wrapper for the original ActiveX control.
- The second class of each pair provides support during the migration process.
In our example, the MSCAL.OCX file contains only one ActiveX control, therefore
only two classes will be created: the class named VB6Calendar inherits from AxMSACAL.AxCalendar
and wraps the ActiveX control; the second class is named VB6Calendar_Support And
inherits from VB6ControlSupportBase. This class will be instantiated and used by
VB Migration Partner during the migration process, but not at runtime during execution.
Before compiling the solution, check the “Instructions” region at the
top of each class. In most cases, the project that AxWrapperGen utility creates
compiles correctly at the first attempt. The compilation process creates a DLL that
you must manually copy into the VB Migration Partner’s main directory.
In some cases, however, you need to tweak the code that has been created. More precisely,
if any property or method has to do with graphic units – in other words, it
represents a width or height and is therefore affected by the container’s
ScaleMode property – you need to shadow the original member and create your
own. For example, the DataGrid control exposes a property named RowHeight, which
requires this treatment. This is the code that implements the required fix:
<Localizable(True)> _
<Category("Appearance")> _
Public Shadows Property RowHeight() As Single
Get
Return VB6Utils.FromPixelY(Me, CInt(MyBase.RowHeight), False, True)
End Get
Set(ByVal value As Single)
MyBase.RowHeight = VB6Utils.ToPixelY(Me, value, False, True)
End Set
End Property
The bulk of the work is done inside the FromPixelY and ToPixelY
methods of the VB6Utils class, contained in the CodeArchitects.VBLibrary.dll. These
methods check the current ScaleMode value and perform all the required conversions.
(Of course, the class exposes also the FromPixelX and ToPixelX
methods, for measures along the X axis.)
For more information about converting ActiveX controls, please read the following
whitepapers:
In general, you can store pragmas inside a *.pragmas file only if the pragma has
a project-level scope. Starting with version 1.50, VB Migration Partner the ability
to store any pragma inside a separate *.pragmas file, including pragmas with
file, method, or variable scope. Pragmas that are added in this way are known as
“positional pragmas”.
To put it simply, a positional pragma is a pragma that VB Migration Partner can
“virtually” insert in the middle of any of the source files the project consists
of. In other words, instead of physically putting the pragma in a VB6 source file,
you provide VB Migration Partner with the information about where it should be inserted.
A positional pragma has one of the following syntax:
filename > statement ==> pragma filename
> statement <== pragma filename > statement
== pragma
where:
- filename is the name of the file where the pragma
should be inserted (must include the extension);
- che > separator marks the end of the file name;
- statement is a line of code inside the file,
that VB Migration Partner can use as a reference point to understand where the pragma
should be inserted; it can also be a fragment of an entire line, provided that there
is only one line in the file that matches the fragment;
- pragma is the pragma to be inserted;
- the ==> separator states that the pragma must be
inserter after the reference line;
- the <== separator states that the pragma must be
inserted befor the reference line;
- the == separator states that the pragma must replace
the reference line.
For example, let’s say that you want to add the DefaultMemberSupport pragma as a
positional pragma inside the DoSomething method of the MainForm.frm file. This is
the text to include inside the VBMigrationPartner.pragmas file
Mainform.frm > Sub DoSomething ==> '## DefaultMemberSupport True
Notice that the reference text isn’t a complete line – method parameters are omitted,
for example – yet it is surely an unambiguous way to point to the method in question.
The ==> causes the DefaultMemberSupport pragma to be inserted after the method definition,
therefore it affects the method itself.
Next, let’s say that you want to add a file-level UseByval pragma to the Widget.cls
class. A minor problem in this case is that you have to find an unambiguous line
of code that appears in the declaration section of the file. A very good candidate
in most cases is the Option Explicit directive, thus the text in VBMigrationPartner.pragmas
file should look like this:
Widget.cls > Option Explicit <== '## UseByVal
You should notice that VB Migration Partner interprets these positional pragmas
and actually inserts the pragma when the VB6 source file is being read. This
detail is important and has two consequences:
- when you browse the VB6 source code inside the VB Migration
Partner’s editor, you actually see the pragma that was added as a positional pragma,
even though the pragma is never physically present in the source code file;
- VB Migration Partner prevents you from saving a VB6
source code file that was affected by one or more positional pragma (else the “virtual”
pragma would be saved as well)
Positional pragmas open up many new possibilities when planning a migration. Thanks
to positional pragmas the team that takes care of the migration process never really
have to interact with the team that maintains the VB6 source code, adds new features,
or fixes bugs. For a successful migration that adheres to the convert-test-fix methodology,
in fact, the migration team only needs to have access to the latest VB6 source files
produced by the other team, and the result of the migration will always be in sync
with the most recent VB6 release.
It is even possible that the two teams never know each other and in fact the positional
pragma feature has been introduced to make it possible for a company to outsource
the migration to another, distinct (and possibly offshore) company.
Finally, please notice that – in spite of the “positional pragma” nickname – this
mechanism is capable to virtually insert any sort of VB6 code in the project being
migrated. You can therefore use it to make minor adjustments to the VB6 codebase
without actually touching it. For example, assume that you would like to “virtually”
change a VB6 statement inside MainForm.frm from
Me.Caption = "My VB6 app"
to
Me.Caption = "My VB.NET app"
Here’s the text line in VBMigrationPartner.pragmas file that does the trick
MainForm.frm > Me.Caption = "My VB6 app" == Me.Caption = "My VB.NET app"
Starting with version 1.50, it is possible to include references to environment variables inside a pragma, so that the variable is expanded when the pragma is interpreted. The syntax for inserting an environment variable inside a pragma is the following:
{%variablename%}
Additionally, VB Migration Partner recognizes two special placeholders, named “Date” and “Time”, which are expanded into the current date and time, respectively.
Environment variable expansions has two major uses. First, it allows you to better document the migrated code, by adding the date/time and the author, as in this example:
'## NOTE Code migrated by {%username%} on {%date%} at {%time%}
During migration the above code code expands to a pragma such as
'## NOTE Code migrated by Joe Doe on 3/24/2012 at 12.34 pm
Second, variable exansions opens up another interesting possibility when multiple developers are migrating the same set of VB6 source code files. For example, assume that Developer A wants to modify the output path of the migrated project so that it points to folder C:\BINS. This can be achieved by means of the following pragma:
'## project:PostProcess "bin\\(?Debug|Release)\\",
"c:\\BINS\\${mod}\\", True, True
Next, let’s assume that Developer B wants to modify the output path of the migrated project so that it points to C:\BINS2. Obviously, he should use a slightly different PostProcess pragma, but if the two developers share the same set of source files only one pragma can have the desired effect.
The solution is using environment variable expansions:
'## project:PostProcess "bin\\(?Debug|Release)\\",
"{%bindir%}${mod}\\", True, True
where the bindir environment variable should be set differently by the two developers. More precisely, Developer A should launch VB Migration Partner by means of a batch file that contains this statement:
Set bindir = c:\\BINS\\
whereas Developer B must include this statement
Set bindir = c:\\BINS2\\