Classes and ActiveX components
Property procedures
A VB6 property is defined by means of its Property Get, Property Let, and Property Set procedures. These procedures are converted into a single
Property…End Property VB.NET block, which can optionally be marked with the
ReadOnly or
WriteOnly keywords if one of the blocks is omitted. During the conversion it is also necessary to account for different scopes of the Property Get block and the Property Let (or Set) block. For example, consider the following VB6 code:
Public Property Get ID() As Integer
ID = m_ID
End Property
Public Property Get Name() As String
Name = m_Name
End Property
Friend Property Let Name(ByVal newValue As String)
m_Name = newValue
End Property
This is how the property must be translated to VB.NET:
Public ReadOnly Property ID() As Short
Get
Return m_ID
End Get
End Property
Public Property Name() As String
Get
Return m_Name
End Get
Friend Set(ByVal newValue As String)
m_Name = newValue
End Set
End Property
Properties with both Let and Set procedures
A VB6 property of Variant type can appear in both a Property Let and a Property Set procedure. VB.NET’s
Property…End Property block supports only one “setter” block, which must merge code from both original blocks. In most cases you can (and should) simplify the code that is generated by converting and merging the VB6 code verbatim. For example, given the following VB6 code:
Property Get Owner() As Variant
If IsObject(m_Owner) Then
Set Owner = m_Owner
Else
Owner = m_Owner
End If
End Property
Property Let Owner(ByVal newValue As Variant)
m_Owner = newValue
End Property
Property Set Owner(ByVal newValue As Variant)
Set m_Owner = newValue
End Property
VB Migration Partner converts this code to VB.NET as follows:
Public Property Owner() As Object
Get
Return m_Owner
End Get
Friend Set(ByVal newValue As Object)
m_Owner = newValue
End Set
End Property
Also, notice that the original Property Let and Property Set procedures might have different visibility – Friend and Private, for example – therefore you have to choose the “broader” visibility (Friend, in this case) when you merge them into a single “setter” block.
Optional parameters in Property procedures
In VB6 it is legal to have a Property Get and a Property Let (or Set) block whose parameters differ for the Optional keyword, as in the following example:
Dim m_Value(10) As String
Property Get Value(ByVal index As Long) As String
Value = m_Value(index)
End Property
Property Let Value(ByVal Optional index As Long, ByVal newValue As String)
m_Value(index) = newValue
End Property
(Notice that this is the only case in which a non-Optional argument can follow an Optional parameter.) In VB.NET the “getter” and “setter” blocks of a Property share the same parameters, therefore they can’t differ for the Optional keyword. In this case, VB Migration Partner uses the Optional keyword for the parameter:
Dim m_Value(10) As String
Property Value(ByVal Optional index As Integer = 0) As String
Get
Return m_Value(index)
End Get
Set (ByVal newValue As String)
m_Value(index) = newValue
End Set
End Property
An even more intricate case occurs when the Property Get and Property Set block differ for the default value of an optional parameter, as in:
Dim m_Value(10) As String
Property Get Value(ByVal Optional index As Long = 0) As String
Value = m_Value(index)
End Property
Property Let Value(ByVal Optional index As Long = -1, ByVal newValue As String)
m_Value(index) = newValue
End Property
While this syntax admittedly makes little sense, it is perfectly legal in VB6. However, there is no way to convert this syntax correctly to VB.NET, thus VB Migration Partner flags it with a migration warning.
Initialize event
VB.NET doesn’t support the Initialize event in classes, forms, and user controls. Any action that needs to be performed when an instance of the class is created should be moved to the class’s constructor.
Terminate event
VB.NET doesn’t support the Terminate event in classes, forms, and user controls. The VB.NET element that is closest to the Terminate event is the
Finalize method, but the two aren’t equivalent. The problem is that VB.NET (and all .NET Framework languages, for that matter) doesn’t support the so-called
deterministic finalization, which means that .NET objects aren’t destroyed when the last reference to them is set to Nothing. This difference causes unpredictable runtime errors after the migration, unless the developer is very careful in how objects are destroyed; in general, the amount of code that must be written to work around the problem isn’t negligible.
VB Migration Partner can generate such code if the
AutoDispose pragma is used.
Default properties (definitions)
In VB6 you can define a field, a property, or a method as the default member of a class. The most common cases of default members are properties exposed by controls, such as the Text property of the TextBox control or the Caption property of the Label control. VB.NET supports neither default fields nor default methods; only default properties are supported and, more important, only properties that have one or more arguments (e.g. the Item property of a Collection). Here’s how you can define a default property in VB.NET:
Default Property Item(ByVal index As Integer) As String
Get
Return m_Items(index)
End Get
Set(ByVal value As String)
m_Items(index) = value
End Set
End Property
Default properties (references)
VB Migration Partner correctly resolves reference to default properties if the variable is strongly-typed. For example, consider the following VB6 method:
Sub UppercaseText(ByVal tb As TextBox)
tb = UCase(tb)
End Sub
VB.NET doesn’t support default parameterless properties, therefore you must explicitly reference the default property:
Sub UppercaseText(ByVal tb As TextBox)
tb.Text = UCase(tb.Text)
End Sub
The actual problem with default parameterless properties becomes apparent when the variable is late-bound, as in this case:
Sub UppercaseText(ByVal ctrl As Object)
ctrl = UCase(ctrl)
End Sub
In this case, VB Migration Partner can correctly resolve the default property at runtime if you enable the corresponding feature with the
DefaultMemberSupport pragma.
Default functions
In VB6 you can define a method as the default member of a class, whereas VB.NET supports only default properties and only if the property takes one or more arguments. For this reason, you should turn the Function into a
Readonly Property block and mark it with the
Default keyword.
VB Migration Partner automatically does this replacement.
Default members and COM clients
If a VB6 class contains a default member and the class is exposed to COM clients, when you translate the class to VB.NET you should mark the default member – be it a field, a property, or a method – with a
System.Runtime.InteropServices.DispID attribute, as in this example:
<System.Runtime.InteropServices.DispID(0)> _
Public Name As String
Member description
You can decorate a VB6 class or class member with a description; such a description appears when the class is explored by means of the VB6 Object Browser. If the class is a user control and the member is a property, the description appears also in the property grid at design time. To implement the same support in a VB.NET class you must convert VB6’s Description attribute to the equivalent XML comment (to display the description in the object browser) and to a
System.ComponentModel.Description attribute:
<System.ComponentModel.Description("Name of the widget")> _
Public Property Name() As String
End Property
Classes and interfaces
VB6 has no notion of interfaces: you define an interface by authoring a VB6 class with one or more empty methods or properties, then use the class’s name in an Implements clause at the top of another class elsewhere in the same project. (If the class that defines the interface is public then the class can implement the interface can reside in a different project.) In VB.NET you have to render interfaces with an explicit
Interface…End Interface block.
In some rare cases, however, a VB6 class is used to define an interface and is also instantiated: in the converted VB.NET program such a class must be rendered as two distinct types: an interface and a concrete class. Consider the following VB6 class named IAddin:
Public Property Get Name() As String
End Property
Public Sub Connect(ByVal app As Object)
End Sub
By default, VB Migration Partner converts this code into an Interface block plus a Class block:
Interface IAddin
ReadOnly Property Name() As String
Sub Connect(ByVal app As Object)
End Interface
Class IAddinClass
Implements IAddin
Public ReadOnly Property Name() As String Implements IAddin.Name
Get
End Get
End Property
Public Sub Connect(ByVal app As Object) Implements IAddin.Connect
End Sub
End Class
You can use the
ClassRenderMode pragma to tell VB Migration Partner that only the Interface block should be generated.
Fields inside interfaces
A VB6 interface – more precisely, a VB6 class that is used to define an interface – can include one or more public class-level fields. Such fields become part of the interface and must be accounted for by classes that implement the interface, typically by including a Property Get and Property Let (or Set) pair of procedures. VB.NET interfaces can’t include fields, therefore the VB6 must be transformed into a property when the class is converted into an Interface…End Interface block. For example, consider the following VB6 class named IAddin:
Public Name As String
Public Sub Connect(ByVal app As Object)
End Sub
VB Migration Partner converts it to VB.NET as follows:
Interface IAddin
Property Name() As String
Sub Connect(ByVal app As Object)
End Interface
Class IAddinClass
Implements IAddin
Private Name_InnerField As String
Public Property Name() As String Implements IAddin.Name
Get
Return Name_InnerField
End Get
Set(ByVal value As String)
Name_InnerField = value
End Set
End Property
Public Sub Connect(ByVal app As Object) Implements IAddin.Connect
End Sub
End Class
Collection classes
VB6 collection classes require that a property or method returns the class’s enumerator object, which the client application can use to iterate over all the elements of the collection. (This object is implicitly requested and used when a For Each loop is encountered.) This method – which is usually named
NewEnum and is usually hidden - must be marked with DispID attribute equal to -4. The enumerator object returned by the NewEnum method must implement the
IEnumVariant interface. However, you can’t implement such an interface with VB6, therefore VB6 collection classes typically return the enumerator object of an inner collection. The following code represents the minimal implementation of a VB6 collection class named Widgets:
Private m_Widgets As New Collection
Public Function Count() As Long
Count = m_Widgets.Count
End Function
Public Function Item(index As Variant) As Widget
Set Item = m_Widgets.Item(index)
End Function
Function NewEnum() As IUnknown
Set NewEnum = m_Widgets.[_NewEnum]
End Function
VB.NET collection classes must implement the
IEnumerable interface and are expected to return an enumerator object through the only method of this interface,
GetEnumerator. In turn, VB.NET Enumerator objects must implement the
IEnumerator interface and its MoveNext, Reset, and Current members.
Class Widgets
Private m_Widgets As New Collection
Public Function Count() As Integer
Return m_Widgets.Count()
End Function
Public Function Item(ByRef index As Object) As Widget
Return m_Widgets.Item(index)
End Function
Public Function NewEnum() As Object
Return m_Widgets.GetEnumerator()
End Function
End Class
VB Migration Partner correctly converts the NewEnum member into the
IEnumerable.GetEnumerator method, even if NewEnum was originally defined as a property. As explained above, the NewEnum member returns the inner collection’s enumerator, therefore the resulting VB.NET collection class never needs to implement the IEnumerator interface. Therefore, VB.NET collection classes converted from VB6 work exactly as expected.
Public COM classes
A public VB6 class defined in an ActiveX EXE or DLL project is visible to COM clients, which can instantiate the class by either a New keyword or the CreateObject method. After the conversion to VB.NET the class must be marked with a
ComVisible attribute to make explicitly visible to existing COM clients, plus a
ProgID attribute that contains the original name of the class:
<System.Runtime.InteropServices.ComVisible(True)> _
<System.Runtime.InteropServices.ProgID("SampleProject.Widget)> _
Public Class Widget
End Class
PublicNotCreatable classes
Public VB6 classes whose Instancing attribute is set to 1-PublicNotCreatable must be converted to public VB.NET classes whose constructor has Friend scope, so that the class can’t be instantiated from outside the project where the class is defined.
Public Class Widgets
Friend Sub New()
End Sub
…
End Class
SingleUse classes
An ActiveX EXE project can define one or more SingleUse and Global SingleUse public classes. SingleUse classes differ from the more common MultiUse classes in that a new instance of the ActiveX process is created any time a client requests an instance of the class. The .NET Framework doesn’t support anything similar to SingleUse classes and it isn’t easy to simulate this feature under VB.NET; moreover, having a distinct process for each instance of a class impedes scalability, therefore it is recommended that you revise the overall architecture so that the application doesn’t depend on SingleUse behavior.
VB Migration Partner ignores the SingleUse attribute and converts SingleUse classes to regular COM-visible VB.NET classes.
Global classes
VB6 supports Global SingleUse and Global MultiUse classes inside ActiveX EXE and DLL projects. (ActiveX DLL projects can’t contain SingleUse classes, though.) There is nothing like global classes in VB.NET, therefore all such classes are handles as regular classes, but the client application instantiates and uses a default instance for each global class, and use it to invoke methods and properties.
VB Migration Partner can convert the global class to a class that contains only Shared members; VB Migration Partner can also convert the global class to a Visual Basic module, if an opportune
ClassRenderMode pragma is used.
DataEnvironment classes
VB.NET doesn’t support DataEnvironment classes. Notice that VB6 supports default instances of DataEnvironment classes, therefore the converted VB.NET application must instantiate such global instances as necessary.
VB Migration Partner converts DataEnvironment classes to special VB.NET classes that inherit from the VB6DataEnvironment base class, and correctly handles default instances; however, it doesn’t converts advanced features such as grouping, relations, and hierarchical DataEnvironment classes.
PropertyPages
The .NET Framework and VB.NET don’t support property pages.
VB Migration Partner converts VB6 property pages to .NET user controls; developers should then write the plumbing code to use and display the user control as appropriate.
UserDocuments
The .NET Framework and VB.NET don’t support user documents.
VB Migration Partner converts VB6 user documents to .NET user controls; developers should then write the plumbing code to use and display the user control as appropriate.
Sub Main in ActiveX DLL projects
If an ActiveX DLL project contains a Sub Main method, the Main method is guaranteed to be executed before any class in the DLL is instantiated. VB6 developers can use this feature to read configuration files, open database connections, and so forth. Conversely, the Sub Main method is ignored inside a DLL authored in VB.NET, therefore code must be written to ensure that initialization chores be performed before any .NET object is created.
VB Migration Partner ensures that the Sub Main is executed before any class in the DLL is instantiated. This is achieved by adding a static constructor to all public classes in the DLL, as in this code:
Public Class Widget
Shared Sub New()
EnsureVB6ComponentInitialization()
End Sub
…
End Class
where the
EnsureVB6ComponentInitialization method is a method that invokes the Sub Main method if
Widget is the first class being instantiated.
MTS components
A public VB6 class defined in an ActiveX DLL project can be made a transactional MTS/COM+ component by setting its MTSTransactionMode attribute to a value other than 0-NotAnMTSObject. VB.NET classes don’t support this attribute: instead, the VB.NET class must inherit from the
ServicedComponent base class and be tagged with the
Transaction attribute whose argument specifies the required transaction level:
Imports System.EnterpriceServices
<Transaction(TransactionIsolationLevel.Required)> _
Public Class MoneyTransfer
Inherits ServicedComponent
End Class
ObjectControl interface
MTS/COM+ components authored in VB6 can implement the ObjectControl interface, which consists of the following three methods: Activate, Deactivate, CanBePooled. VB.NET components that run under COM+ must not implement the ObjectControl interface; instead, they must override the
Activate,
Deactivate, and
CanBePooled methods that they inherit from the
System.EnterpriseServices.ServicedComponent base class.
VB Migration Partner automatically converts ObjectControl methods into the corresponding VB.NET overrides.
IObjectConstruct interface
MTS/COM+ components authored in VB6 can grab the construction string defined in Component Services applet by implementing the IObjectConstruct interface, which consists of just one method, Construct. This method receives an object argument, whose ConstructString property returns the construction string:
Private Sub IObjectConstruct_Construct(Byval pCtorObj As Object)
Dim connStr As String
connStr = pCtorObj.ConstructString
End Sub
VB.NET components that run under COM+ must not implement the IObjectConstruct interface; instead, they must override the
Construct method that they inherit from the
System.EnterpriseServices.ServicedComponent base class; the only argument that this method receives is the construction string:
Protected Overrides Sub Construct(Byval connStr As String)
End Sub
Persistable classes
Public VB6 classes in ActiveX EXE and DLL projects can be made persistable, by setting their Persistable attribute to 1-Persistable. VB.NET doesn’t support the Persistable attribute: a VB.NET class can be persisted to file - or passed by value to an assembly living in a different AppDomain – if the class is marked with the
<Serializable> attribute.
VB Migration Partner converts persistable VB6 classes into VB.NET classes that are marked with the <Serializable> attribute and that implement the
ISerializable interface.
InitProperties, WriteProperties, and ReadProperties events
Persistable VB6 classes can handle the InitProperties, WriteProperties, and ReadProperties events, which fire – respectively – when the class is instantiated, when the COM infrastructure needs to store the object’s state somewhere, and when the object is asked to restore a previous state. These events aren’t supported by the .NET Framework: a VB.NET classes requiring custom serialization must implement the
ISerializable interface and therefore implement the
GetObjectData method and the special constructor that this interface implies.
VB Migration Partner extracts the code from the InitProperties, WriteProperties, and ReadProperties events and uses it inside GetObjectData method and the special constructor implied by the ISerializable interface.
ADO data source and data consumer classes
VB6 allows you to create databinding-aware classes, none of which are supported by VB.NET. More precisely, in VB6 you can create
- ADO data source classes or user controls (by setting the DataSourceBehavior attribute to 1-vbDataSource); for example you might create a custom version of the AdoDC control and bind other controls to it.
- ADO data consumer classes or user controls, that can be bound to an AdoDC control, a DataEnvironment object, an ADO Recordset object, or an ADO data source object. Two different flavours of data consumer classes are supported: simplex-bound (DataBindingBehavior=1-vbSimpleBound) and complex-bound (DataBindingBehavior=2-vbComplexBound). For example, a textbox-like user control might be defined as a simple-bound consumer class, because it displays data taken from a single record exposed by the data source, whereas a grid-like user control might be defined as a complex-bound class, because it displays data from multiple records.
VB Migration Partner supports data source classes and simple-bound data consumer classes and user controls (but not complex-bound data consumer classes and user controls).
AddIn classes
VB6 addin classes aren’t supported by VB.NET. Microsoft Visual Basic 6 and Microsoft Visual Studio 2005’s object models are too different for this feature to be migrated automatically. (Manual translation isn’t exactly easier either.)
VB Migration Partner doesn’t support addin clases.
WebClass components
VB.NET support WebClass components. VB6 applications that used WebClass components should be converted to ASP.NET for better speed, more power, and easier maintenance.
The Upgrade Wizard provides a limited support to WebClass components; VB Migration Partner doesn’t support these components at all.
DHTML Page components
VB.NET doesn’t support DHTML Page components.
VB Migration Partner drops these components when converting VB6 applications, and emits one migration warning for each DHTML Page component.