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.
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
Unreachable code detection in current version of VB Migration Partner accounts neither
for conditional structures (e.g. If blocks) nor for unhandled errors. For example,
in the following example the code analyzer fails to detect that the call to Test2
is unreachable:
Public Sub Test(ByVal x As Integer)
If x < 0 Then
Exit Sub
Else
Err.Raise 99
End If
Test2()
End Sub
Unneeded Class_Terminate events
This feature requires VB Migration Partner Enterprise edition
Many VB6 developers define a Class_Terminate event handler just to set class-level
variables to Nothing. Such assignments are superfluous – because the object
is released anyway when the “Me” object is destroyed, but this coding
style doesn’t harm in VB6.
When you convert this code to VB.NET, however, the Class_Terminate handler is translated
into a Finalize method. As you may know, finalizable VB.NET classes are less efficient
than standard classes, therefore you should avoid to declare the Finalize method
unless it is strictly necessary. To help you follow .NET Framework coding best practices,
VB Migration Partner emits an UPDATE_INFO remark when a Class_Terminate event handler
can be safely removed, as in this case:
Private col As New Collection
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:
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>"
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
Developers can write extenders to receive notifications from VB Migration Partner
during the parse and code generation process. An extender class can access VB Migration
Partner’s object model, read pragmas (and dynamically add new ones), edit
the VB6 code before it is being parsed, modify the VB.NET code after it has been
generated, and so forth.
The following extender class inserts a remark at the top of all VB.NET code files
produced during the migration process:
<VBMigratorExtender("My first extender", _
"A demo extender for CodeArchitect's VB Migration Partner")> _
Public Class DemoExtender
Implements IVBMigratorExtender
Public Sub Connect(ByVal data As ExtenderData) _
Implements IVBMigratorExtender.Connect
End Sub
Sub Notify(ByVal data As ExtenderData, ByVal info As _
OperationInfo) Implements IVBMigratorExtender.Notify
If info.Operation = OperationType.ProjectItemConverted Then
Dim remark As String = "Generated by VB Migration Partner" & vbCrLf
info.ProjectItem.NetCodeText = _
remark & info.ProjectItem.NetCodeText
End If
End Sub
End Class
All pragmas are visible to extenders. Even better, an extender can programmatically
create new instances of pragmas and associate them with code entities, as if they
were embedded in the VB6 code being migrated. This feature enables developers to
overcome that static nature of pragmas hard-coded in VB6 code. For example, the
decision to treat a variable as an auto-instancing variable might be taken after
checking how the variable is used, as in this code:
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 IVBMigratorExtender.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.)
Note: The SDK library for building extenders will be
released some time after the launch of VB Migration Partner.
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 VS2005 projects. You can generate VS2008 projects by adding a /version option:
AxWrapperGen c:\windows\system32\mscal.ocx /version:2008
AxWrapperGen runs the AxImp tool – which is part of the .NET Framework SDK
– behind the scenes, to generate two DLLs that work as the RCW (Runtime Component
Wrapper) for the selected control. For example, the mscal.ocx control generates
the following two files: msacal.dll and axmsacal.dll. You can specify the following
five options, which AxWrapperGen passes to AxImp: /keyfile, /keycontainer, /publickey,
/delaysign, and /source. For more information, read .NET Framework SDK documentation.
At the end of generation process, AxWrapperGen runs Microsoft Visual Studio and
loads a solution that contains one VB.NET class library (DLL) project, containing a pair
of classes for each ActiveX control:
- The first class of each pair works as a wrapper for the original ActiveX control.
- The second class of each pair provides support during the migration process.
In our example, the MSCAL.OCX file contains only one ActiveX control, therefore only
two classes will be created: the class named VB6Calendar inherits from AxMSACAL.AxCalendar and
wraps the ActiveX control; the second class is named VB6Calendar_Support And inherits from
VB6ControlSupportBase. This class will be instantiated and used by VB Migration Partner
during the migration process, but not at runtime during execution.
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.)
VB Migration Partner supports automated conversions by means of the VBMP.EXE command-line too. You can use this tool inside batch files and in MSBuild projects. Its syntax is quite simple:
VBMP projectfile options
Where projectfile is the complete path of the VB6 project (or project group) file, and options is zero or more of the following:
/out:path specifies the parent directory of the folder that will be created for the VB.NET solution. This option corresponds to the path you can enter in the Save tab of the Tools-Options dialog box.)
/version:VVVV specifies the Visual Basic version of the code to be output. VVVV can be either 2005 or 2008. (The default is 2005.)
/maxissues:NN specifies the max number of conversions issues. If the conversion process generates more issues than this value, the VBMP utility exits with a non-zero errorlevel and doesn’t proceed with the compilation step even if the /compile option is defined. (If omitted, this value is equal to 10.)
/compile causes the converted VB.NET to be compiled.
/quiet suppresses most messages.
/help or /h displays a brief explanation of each option.
For example, the following command compiles the c:\vb6\widgets.vbg project and creates a VB2008 solution in the c:\vbnet\widgets folder, then compiles the result unless the conversion process generated more than 30 migration issues:
VBMP c:\vb6\widgets.vbg /out:c:\vbnet /compile /maxissues:30 /version:2008
If the specified VB6 project can’t be converted, or the conversion was aborted because of a fatal error, or the conversion produced more issues than allowed, or if the compilation step produced one or more errors, then VBMP returns a nonzero errorlevel to the operating system.