New KB articles about version 1.34

clock October 17, 2011 12:55

As explained in my previous post, VB Migration Partner 1.34 is a major update that contains many great features that make the most powerful VB6 converter on the market even more powerful.

If you are using the new version to re-migrate VB6 projects that have been instrumented with pragmas - in other words, you are adopting our convert-test-fix methodology - the notes that follow aren't really pertinent to you. On the other hand, if you plan to simply replace the current version of CodeArchitects.VBLibrary DLL with the new version there are a few things you should know.

  • we have changed the way the ImageList control is implemented, and this change may affect existing (migrated) forms. You can read more in this article
  • If you take advantage of the new CodeArchitects.VBPowerPack DLL - for example by copying it into VB Migration Partner's setup folder - you should consider that a few VB6 objects will be rendered by means of objects exposed by this new DLL, namely: the CommonDialog, Line, and Shape controls, the Printer object, and the Printers collection. The objects in this DLL are fully .NET classes that have no dependency on COM, however they are slightly less compatible with VB6 than the objects used by VB Migration Partner v.1.33 (that are still available in the main CodeArchitects.VBLibrary support library). Please read this article before deciding whether to use the new objects.

VB Migration Partner 1.34 introduces many new features that can greatly improve the behavior of your migrated projects. For example:

  • The new VB6Config.UseExCompositeStyle boolean property allows you to leverage a little-known feature of .NET forms and eliminate the flickering you may see when loading or resizing a form that contains many controls. (The flickering is especially apparent if the form has a background image). This article explains how and when to use this property.
  • The new VB6Config.DBCSSupport boolean property can be set to true to improve support for DBCS strings. This is especially useful for Japanese users. (Don't set this property to True unless necessary, because it would slightly reduce exection speed of some string functions.)
  • If the new VB6Config.UseVBPowerPack boolean prooperty is set to True, then the PrintForm method internally uses the PrintForm class defined in Microsoft VB PowerPacks. In some cases this setting delivers better results. Of course, if you assign True to this property the Microsoft.VisualBasic.PowerPacks.dll file must be installed on the end user's computer.
  • The VB6Form, VB6PictureBox, and VB6UserControl classes now expose the DoubleBuffered property. If you set this property to True - in the Properties window at design-time or programmatically via code - applications that perform massive graphic operations will run remarkably faster, up to a factor of 8x! The speed improvement is especially noticable for graphic methods that run inside the Paint event handler.
  • The CommandButton, CheckBox, and OptionButton controls now expose a boolean property named UseTextAlignment. If you set this property to True - in the Properties window at design-time or programmatically via code - then you can supercede the standard VB6-styled text alignment and precisely align the caption by means of the .NET TextAlignment property.
  • A new migration INFO message is generated for all Variant and Object variables whose type has been inferred into a more specific type. Thanks to this new message you can quickly check that VB Migration Partner inferred the correct type.

VB Migration Partner 1.34 has also fixed a few minor bugs and further improved the compatibility with VB6 in many areas.

 



Does a support library add any performance overhead?

clock May 9, 2011 13:08


This is a recurring question among our prospect customers. The general idea is that a support library can't avoid adding some overhead to the migrated application.

However, this is just another false myth and in fact we can easily prove that a support library can often make your application faster, not slower. Let's divide the explanation in four parts.

1) Basic member wrappers
The vast majority of the classes and methods in VB Migration Partner's library are just very thin layers over the corresponding .NET control. For example, this is the code for the SelText property of the VB6TextBox control:

Public Class VB6TextBox
   Inherits System.Windows.Forms.TextBox

   ' ...(all other properties and methods have been omitted)

   Public Property SelText() As String
      Get
         Return MyBase.SelectedText
      End Get
      Set(ByVal value As String)
         MyBase.SelectedText = value
      End Set
   End Property
End Class

NOTICE that the VB6TextBox class inherits from System.Windows.Forms.TextBox, therefore it is a 100% native .NET control. Only the few members whose name or behavior differ from the VB6 counterpart need to be overridden. Also, notice that re-exposing a .NET property with the VB6 name ensures that migrated apps work also if the control is accessed via late-binding. None of our competitors can’t handle the late-binding case correctly and they require that you manually tweak the migrated code.

As you see, the wrapping property just forward the call to the base class. Interestingly, the .NET Just-in-time compiler uses an optimization technique known as code inlining, which guarantees that the SelText property is sidestepped and that the client app directly invokes the SelectedText member in the base class. In short, no performance penalty occurs for simple wrapping members.

2) Wrappers with additional statements
A method in the support library may include additional statements, in order to preserve functional equivalence with the original VB6 code. For example, in VB6 an assignment to the SelStart property also resets SelLength and brings the cursor into view. We keep the functional equivalence by adding a couple statements in the wrapping property:

Public Property SelStart() As Integer
   Get
      Return MyBase.SelectionStart
   End Get
   Set(ByVal value As Integer)
       MyBase.SelectionStart = value
          MyBase.ScrollToCaret()
          ' in VB6 setting SelStart resets Sellength
          MyBase.SelectionLength = 0
   End Set
End Property

The code inlining optimization technique described above avoids the call overhead even in this case, yet it is true that these additional actions may introduce a minor overhead.

The point not to be missed, however, is that developers working at the migration should add those statements anyway to preserve functional equivalence. Having these statements located in the library instead of in the migrated code brings several advantages, including

  • Developers save time because apps migrated work well at the first attempt
  • Developers don’t need to be “migration gurus” nor they need to be aware of the thousands major and minor differences between VB6 and .NET
  • The code in the migrated app is more readable and can be maintained more easily, because it doesn’t contain all the extra (and obscure) statements that ensure that VB.NET code works exactly like VB6.
  • The migrated app is therefore smaller and loads faster

Please notice that over 95% of the methods and properties defined in our support library fall in either case 1 or 2. None of them adds any performance penalty to migrated apps. Better, they make your code more concise and faster!

3) Methods that have no .NET equivalent
Fewer than 5% of the members in VB Migration Partner's support library have no direct equivalent in the .NET Framework. For example, this is the case with graphic methods (Line, Circle, etc.), drag-and-drop statements, and DDE keywords. Of course you *CAN* implement graphics and drag-and-drop under .NET, but consider that this isn't an easy task because the VB6 and .NET programming models are very different. Not surprisingly, only VB Migration Partner can automatically convert these VB6 features, thanks to its support library.

Because is no direct .NET counterpart exists and no other VB6 conversion tool supports these features, it's impossible to measure the overhead that our library adds in these cases. At any rate, we concede that an expert .NET developer might be able to re-write the graphic or drag-and-drop portions of your software so that it runs faster than the code migrated from VB6.

As usual, the decision is a trade-off of cost/time against performance. VB Migration Partner can convert a graphic-intensive VB6 application into a reasonably fast .NET graphic application. Maybe you can make youre code run faster if you manually rewrite those graphic statements using native GDI+ methods. On the other hand, you're surely going to spend a lot of time and money in the process, without any certainty to achieve a result that is noticeably faster than what VB Migration Partner delivers to you in a few seconds and for free.

4) Helper performance-wise classes and methods
Finally, don't forget that in many cases when a support library can actually make your migrated code faster, not slower. VB Migration Partner's support library includes several helper classes that have been designed with performance in mind. A great example of this concept is the StringBuilder6 helper class, that can speed up string concatenation by two or three orders of magnitude, as explained here.

The StringBuilder6 class is nothing but a wrapper for the well-known System.Text.StringBuilder class, thus you might optimize the migrated code by using either class. The big difference is that using the StringBuilder6 class only requires that you insert one single SetType pragma in the original VB6 code, whereas using the standard StringBuilder forces you to manually modify the generated code in many places, because the StringBuilder object calls for a different syntax.

VB Migration Partner's library also offers auxiliary classes to speed up collections, and the VB6Collection helper class is much faster than the standard VB.NET collection in nearly all circumstances, as explained in this article.

VB Migration Partner's support library is written in standard VB.NET, therefore all the performance improvements it offers could be theoretically achieved by modifying the VB.NET code generated by any conversion tool, for example Artinsoft's VBUC. However, these manual optimizations require some time (often, a LOT of time) and are error-prone, therefore in practice they are seldom carried out unless the migrated code is just too sluggish to be used.

 

Bottom line: VB Migration Partner and its support library generate .NET apps that run faster than those produced by any other competing product, and rarely require that you manually optimize the generated code.

Still not convinced? Then wait no longer: ask for VB Migration Partner Trial Edition and compare it against any other VB6 conversion tool, or against your .NET programming skills. The faster wins.



Generating auto-implemented properties for Visual Basic 2010

clock April 7, 2010 01:55

As our customers already knows, starting with version 1.30 VB Migration Partner can generate Visual Basic 2010 projects and solutions. In this short article I'll illustrate how you can generate code that leverages one of the coolest new features of VB2010: auto-implemented properties.

To see when and why auto-implemented properties can be useful, consider that most properties are just wrappers for a private variable, as in the following example:

Private m_ID As Long
Private m_Name As String

Public Property Get ID() As Long
    ID = m_ID
End Property

Public Property Get Name() As String
    Name = m_Name
End Property

Public Property Let Name(ByVal value As String)
    m_Name = value
End Property

When converted through VB Migration Partner, the above code generates these VB.NET statements:

Private m_ID As Integer
Private m_Name As String = ""

Public Readonly Property ID() As Integer
    Get
        Return m_ID
    End Get
End Property

Public Property Name() As String
    Get
        Return m_Name
    End Get
    Set(ByVal value As String)
        m_Name = value
    End Set
End Property


In cases like this – that is, when a property is merely a wrapper for a private variable and doesn’t perform any additional action – you can generate a new cool feature of Visual Basic 2010: auto-implemented properties. In fact, in VB2010 you can rewrite the above code block with just two lines:

Public ReadOnly Property ID As Integer
Public Property Name As String = ""


VB Migration Partner doesn’t include an explicit option to generate auto-implemented properties. However, you can easily tweak the converted VB2010 code to reach this goal. In fact, you just need three pragmas:

'## REM tranform the variable declaration into an implemented property
'## PostProcess "\b(Dim|Private)\s+m_(?<prop>Name)(?<type>\s+
        As\s+.+\r\n)", "Public Property ${prop}${type}"

'## REM same as above, but this is for readonly properties
'## PostProcess "\b(Dim|Private)\s+m_(?<prop>ID)(?<type>\s+
        As\s+.+\r\n)", "Public ReadOnly Property ${prop}${type}"

'## REM delete the Property procedures
'## PostProcess "[ \t]*(Public|Friend|Private)\s+(ReadOnly\s+)?Property
        \s+(ID|Name)\b.+\r\n(.+\r\n)+?\s*End Property\r\n", ""


The interesting point is that you need only three pragmas regardless of how many properties you want to process. For example, suppose that you have a class that contains 8 properties that can be transformed into auto-implemented properties:
    FirstName, LastName, Address, City, ZipCode, Country  (read-write)
    ID, Age (read-only)
These are the three pragmas that you need:

'## PostProcess "\b(Dim|Private)\s+m_(?<prop>FirstName|LastName|Address|
        City|ZipCode|Country)(?<type>\s+As\s+.+\r\n)",
        "Public Property ${prop}${type}"

'## PostProcess "\b(Dim|Private)\s+m_(?<prop>ID|Age)(?<type>\s+As\s+
        .+\r\n)", "Public ReadOnly Property ${prop}${type}"

'## PostProcess "[ \t]*(Public|Friend|Private)\s+(ReadOnly\s+)?Property\s+
        (FirstName|LastName|Address|City|ZipCode|Country|ID|Age)\b.+\r\n
        (.+\r\n)+?\s*End Property\r\n", ""

Cool, uh?

NOTE: If the original VB6 code accesses the private variable outside the property procedure, you need one more pragma to ensure that all references to the variable are rendered as referenced to the property

'## PostProcess "\bm_(?<prop>FirstName|LastName|Address|City|ZipCode|
        Country|ID|Age)\b", "${prop}"

Also notice that if the property is marked as ReadOnly and you access the inner variable in places other than the Class_Initialize event handler, than you can't render the property as an auto-implemented property.

 



Code Architects' ADOLibrary, a revolutionary approach to ADODB to ADO.NET migration

clock October 2, 2009 08:32

When migrating a VB6 legacy application, obtaining a working piece of VB.NET code is only part of the story. To complete the transition to the .NET world you also want to convert your database-access code to ADO.NET.

The Upgrade Wizard that comes with Visual Studio doesn't offer any option to upgrade ADODB code, with a good reason: "traditional" code translators can't automatically convert from ADODB to ADO.NET, no matter how sophisticated the translator is. (More on this below)

Fortunately, VB Migration Partner isn't a "traditional" code conversion. We have already proved that it correctly translates many VB6 features and keywords that many "migration experts" swore that were impossible to translate automatically. VB Migration Partner 1.20 supports *all* the major VB6 features, inclduing Gosubs, Variants, graphic statements, drag-and-drop, DDE, and a lot more.

After we dealt with the complete set of VB6 features, we decided to focus our attention on the challenges of porting ADODB-based code to ADO.NET.

The result of our efforts is Code Architects' ADOLibrary, a revolutionary tool that makes ADODB-to-ADO.NET migration one or two orders of magnitudes simpler, faster, and less expensive.

In a nutshell, ADOLibrary is a set of native .NET classes that behave exactly like their ADODB counterparts. For example, the ADOConnection object supports all the methods, properties, and events of the ADODB.Connection object, with exactly the same syntax and the same behavior. The same holds true for the ADORecordset class, the ADOCommand class, and ancillary classes such as ADOParameter, ADOField, and ADOProperty.

ADOLibrary is currently in internal beta stage, but it already supports forwardonly-readonly cursors and client-side cursors with optimistic batch updates. We even and transparently support advanced ADODB programming techniques, such as asynchronous events and alternate update strategies (the Update Criteria property), for the benefit of demanding ADODB gurus out there.

With its vast support for most ADODB features, ADOLibrary enables you to migrate thousands of database-related statements in a matter of hours. This is perhaps 100-200x faster than a manual migration and 10-20x more productive than any other code translator on the market. In practical terms, it can save you weeks or months in a real-world migration project and significantly reduce migration costs.

The first ADOLibrary example

Let me give you an idea of what the ADOLibrary works. Say that you have the following piece of VB6/ADODB code:

    Dim cn As New ADODB.Connection
    Dim rs As New ADODB.Recordset
    ' connect to Northwind
    cn.Open "Provider=SQLOLEDB.1;Initial Catalog=Northwind;..."
    cn.CursorLocation = ADODB.adUseClient
    rs.Open "Select * from Products ", cn,
ADODB.adOpenStatic, _
       
ADODB.adLockBatchOptimistic
    
    ' Load a listbox with all product names
    Do Until rs.EOF
        ListBox1.AddItem rs.Fields("ProductName").Value
        rs.MoveNext
    Loop


Here's the VB.NET code that uses the ADOLibrary, as generated by VB Migration Partner:

    ' Assumes: Imports CodeArchitects.ADOLibrary

   
Dim cn As New ADOConnection
   
Dim rs As New ADORecordset
    ' connect to Northwind
    cn.Open("Provider=SQLOLEDB.1;Initial Catalog=Northwind;...")
    cn.CursorLocation = ADOCursorLocation.adUseClient
    rs.Open("Select * from Products ", cn, ADOCursorType.adOpenStatic, _
        ADOLockType.adLockBatchOptimistic)
    
    ' Load a listbox with all product names    
   
Do Until rs.EOF
        ListBox1.AddItem(rs.Fields("ProductName").Value)
        rs.MoveNext()
   
Loop

As you see, the VB.NET code is basically the same as the original VB6 code, except for the different class and enum names. Even more important, the code is perfectly equivalent to the original VB6 code

If you are an ADODB developer you will find yourself comfortable with the generated code, which is as maintainable as the original VB6 code, except it leverages ADO.NET's superior performance and scalability. You are immediately productive and don't have to study ADO.NET, because our ADOLibrary hides the many differences between the two object models.

Don't let the ADODB-like syntax fool you, though: ADOLibrary relies on ADO.NET objects exclusively and has no dependency on COM or ADODB. It's like having ADO.NET with an ADODB dress.

But the ADOLibrary is more than just an ADODB clone, because it exposes all the ADO.NET structures that it uses internally in addition to all the properties and methods "inherited" from ADODB.

For example, the ADORecordset object exposes the inner DataTable object that stores the individual records. You can therefore bind the "recordset" to .NET controls and data grids. Or you can optimize the converted code as follows, and improve it to keep track of both the product name and the product ID:

    ' Load a listbox with all product names
    ListBox1.ValueMember = "ProductID"
    ListBox1.DisplayMember = "ProductName"
    ListBox1.DataSource = rs.DataTable     ' use native .NET binding!


Thanks to the DataTable member, you can rely on .NET databinding to implement master-detail relationships, advanced data formatting, input validation, and so forth. Just keep in mind is that all these additions and fixes are optional, because the converted .NET code will work immediately as-is.


Why you can't obtain ADODB-to-ADO.NET functional equivalence with a traditional code translator

At least another VB6 conversion tool vendor claims that their product can handle ADODB to ADO.NET conversions by means of code transformation techniques. However, they don't go into many details about which features are converted and which ones require some (or a lot of) manual fixes.

The problem in automatic conversion of ADODB code to ADO.NET is two-fold. First and foremost, it is impossible to achieve full functional equivalence. Second, filling the gap between ADODB and ADO.NET by means of code transformations is that you end up generating verbose (and often illogical) code that is very hard to maintain and evolve in the future.

Consider the following trivial code snippet:

    Set cnNorthwind As New ADODB.Connection
   
Set rsOrders As New ADODB.Recordset
   
cnNorthwind.Open "Provider=SQLOLEDB;Data Source=.;Initial Catalog=Northwind;Integrated Security=SSPI"
    rsOrders.Open "SELECT * FROM Orders", db,
ADODB.adOpenKeyset, _
        ADODB.
adLockOptimistic

Notice that this example uses server-side keyset cursors, that aren't supported by ADO.NET. Keysets are typically used when you need to modify data in the database, therefore the closest ADO.NET structure that can perform the same task is the combination of a DataSet plus a DataAdapter. This is the best code that a conversion tool that relies solely on code transformation can generate

    Dim cnNorthwind As New SqlConnection
   
Dim rsOrders As New DataSet
    cnNorthwind.Open("
Provider=SQLOLEDB;Data Source=.;Initial Catalog=Northwind;Integrated Security=SSPI")
   
Dim cmd As New SqlCommand
   
cmd.Connection = cnNorthwind
   
cmd.CommandText = "SELECT * FROM Orders"
   
Dim da As New SqlDataAdapter(com.CommandText, com.Connection)
    adap.Fill(rs)

Up to here, the converted code behaves more or less like the original VB6 code. However, the actual problems arise now. For example, consider that

  • the original ADODB code typicall uses a Do Until rsOrders.EOF loop to visit and possibly modify each record in the database. The loop can be transformed into a For Each loop that visits all the rows in the DataTable, but the database is updated only when the DataAdapter's Update method is invoked.
  • when using a keyset on a locked record, you get a runtime error when you modify one or more fields and then move away from the current record. Conversely, the optimistic lock used by the DataAdapter.Update throws a single error when any record in the DataTable fails to update. The two models greatly differ, so expect a lot of manual fixes in this area.
  • The example shows a simple SELECT query, however many VB6 apps retrieve (and update) by means of queries or stored procedures with parameters. ADODB and ADO.NET mechanisms for retrieving and using stored proc parameters are very different and this gap can't be filled by writing some code. Besides, the .NET CommandBuilder.DeriveParameters method has several defects and can't be used in production code, thus in practice you are forced to define all the query parameters manually in code.

It isn't at all surprising that our competitors don't provide any code sample that shows how their tool converts these (and many other) ADODB basic features, such as Recordset and Connection events, transactions, and bookmarks. Not to mention more advanced ones such as the Update Criteria property and the Resync method.

In practice, we estimate that a traditional code translator can automate no more than 10-15% of the statements that read, write, and process data stored in a database. Is 15% enough to qualify for ADODB-to-ADO.NET automatic conversion?

Our competitor also claims that their tool can convert code in a database-agnostic fashion. In .NET parlance, it means that they generate code that uses objects in the System.Data.Common namespace exclusively, e.g. DbConnection and DbCommand. You might believe that it's a good approach at writing database-agnostic code, until you see how ADOLibrary solves the same problem.

Internally, ADOLibrary uses the .NET objects in the System.Data.Common namespace, therefore it is inherently database-agnostic. When it's time to create a concrete class, by default ADOLibrary instantiates the objects in the System.Data.OleDb namespace (e.g. OleDbConnection, OleDbCommand, and so forth), and is therefore as database-agnostic as the original ADODB code. However, this is just the default behavior and you can override it by simply setting a global property.

For example, if your application is designed to work with Microsoft SQL Server exclusively, you can have ADOLibrary instantiate concrete classes in the System.Data.SqlClient namespace, which delivers faster and more scalable code. To enable this optimization you just need to add the following single statement:

    ADOConfig.LibraryKind = ADOLibraryKind.SqlClient

That's it! No need to be confused with base classes or other similar annoyances! Wink

For even more flexibility, you can read the value of the LibraryKind property from the configuration file, to let your end users customize the application for the database they actually use, without you having to recompile your code. Talk about flexibility!



Accomplishing the impossible: VARPTR in VB.NET!

clock March 16, 2009 02:39

VB6 supports three functions – VarPtr, StrPtr, and ObjPtr – that were never officially supported by Microsoft, yet they have been extensively used by many expert VB6 developers. In fact, you badly need these functions when calling some especially complex Windows API functions. Of these functions, the most useful one is VarPtr, which returns the address of the memory location where the value of a variable is stored.

Neither the Upgrade Wizard nor any other VB6 migration tool supports the VarPtr keyword, thus I decided that our VB Migration Partner *had* to correctly convert it. Thanks to Google, I found out that the problem had been discussed at length in many forums, but no definitive solution has ever been found yet. You can write an implementation in unsafe C#, or an unmanaged DLL written in C or Delphi, but these solution would force us to distribute a separate DLL with VB.NET apps converted by VB Migration Partner, which I’d rather not to.

It took a while and a lot of thinking, but in the end I figure out a way to solve the problem. Yes, it is possible to write a VarPtr function in plain VB.NET, with only the help of a method exposed by the Windows API. Actually, you need just a few lines of code:

' -----------------------------------------------------------
' VARPTR implementation in VB.NET
' Part of VB Migration Partner’s support library
'
' Copyright © 2009, Francesco Balena & Code Architects
' -----------------------------------------------------------

Module VarPtrSupport
   ' a delegate that can point to the VarPtrCallback method
   Private Delegate Function VarPtrCallbackDelegate( _
     
ByVal address As Integer, ByVal unused1 As Integer, _
      ByVal
unused2 As Integer, ByVal unused3 As IntegerAs Integer


   ' two aliases for the CallWindowProcA Windows API method
   ' notice that 2nd argument is passed by-reference
   Private Declare Function CallWindowProc Lib "user32" _
      Alias
"CallWindowProcA" _
      (ByVal wndProc As VarPtrCallbackDelegate, ByRef var As Short, _
      ByVal unused1 As Integer, ByVal unused2 As Integer,  _
     
ByVal unused3 As Integer) As Integer

   Private Declare Function CallWindowProc Lib "user32" _
      Alias "CallWindowProcA" _
      (ByVal wndProc As VarPtrCallbackDelegate, ByRef var As Integer, _
      ByVal unused1 As Integer, ByVal unused2 As Integer, _
     
ByVal unused3 As Integer) As Integer
   ' ...add more overload to support other data types...

   ' the method that is indirectly executed when calling CallVarPtrSupport
   ' notice that 1st argument is declared by-value (this is the
   ' argument that receives the 2nd value passed to CallVarPtrSupport)

   Private Function VarPtrCallback(ByVal address As Integer, _
        
ByVal unused1 As Integer, ByVal unused2 As Integer, _
         ByVal unused3 As Integer) As Integer
      Return address
   End Function


  
' two overloads of VarPtr
   Public Function VarPtr(ByRef var As Short) As Integer
      Return CallWindowProc(AddressOf VarPtrCallback, var, 0, 0, 0)
   End Function

   Public Function VarPtr(ByRef var As Integer) As Integer
      Return CallWindowProc(AddressOf VarPtrCallback, var, 0, 0, 0)
   End Function

   ' ...add more overload to support other data types...
End Module

To understand how the trick works, let’s see what happens when you call the VarPtr method. The only line of code in this method invokes one of the overloads of CallWindowProc method. The CallWindowProc method takes five arguments, the first one of which is a delegate that must point to a method that takes four 32-bit values. CallWindowProc invokes the method pointed to by the delegate and passed the other four values to such a method.

The key point in this mechanism is that each CallWindowProc overload takes a value by-reference in its second argument – a Short and an Integer, respectively. This means that the CallWindowProc method (buried inside the User32.dll) receives the address of the Short or Integer variable. This address is a 32-bit integer and is passed verbatim to the VarPtrCallback method. This method in turn receives a 32-bit integer value with by-value semantics, which means that the address parameter now contains whatever value was pushed on the stack by the CallWindowProc method.

Let’s quickly recap: the VarPtr method pushes the address of the Short or Integer variable – that is, the value we are interested in – on the stack. This 32-bit integer is received by the CallWindowProc method (in User32.dll) and is sent to the VarPtrCallback method, which receives it in its first argument and returns it verbatim to the CallWindowProc method, which in turn returns it to the VarPtr method that can finally return it to the caller.

Notice that you might need to add more overloads for the VarPtr method (and the CallWindowProc method), to support data types other than Short or Integer. Just remember that you can’t use this technique with String, Objects, or other reference types. It doesn’t work with Boolean values, either.

Interestingly, you can use the VarPtr method with structures, provided that the structure doesn’t contain any String, Object, or Boolean elements. To get the address of a structure just use VarPtr on its first element, as in this example:

Structure POINTAPI
   Public x As Integer
   Public y As Integer

End Structure

Dim
pnt As POINTAPI
Dim addr As Integer = VarPtr(pnt.x) 

To prove that the VarPtr function works correctly, let’s use it together with the CopyMemory Windows API method to delete an element in an array by quickly shifting all the elements towards lower indices:

Declare Sub CopyMemory Lib "Kernel32.dll" Alias "RtlMoveMemory" _
   (ByVal dest As Integer, ByVal source As Integer, _
    ByVal numBytes As Integer)


Sub
Main()
   Dim arr(1000) As Integer
   ' initialize the array
  
For n As Integer = 0 To UBound(arr) : arr(n) = n : Next
   ' ...
   ' delete first element by shifting all elements towards the beginning
   '
of the array, then clear last element
   CopyMemory(VarPtr(arr(0)), VarPtr(arr(1)), UBound(arr) * 4)
   arr(UBound(arr)) = 0
   ' check that it worked fine
   For n As Integer = 0 To UBound(arr) - 1
      If arr(n) <> n + 1 Then Debug.Print("Wrong value at index {0}", n)
   Next

End Sub

In case you are wondering why you should use CopyMemory and VarPtr to shift all the elements of an array – instead of a plain For … Next loop – the answer is: execution speed. Under VB6 this technique was often used to significantly speed up array operations; the VB.NET compiler produces more efficient code and this technique is seldom necessary, nevertheless the ability to convert this code from VB6 without any major edits means that you don’t have to spend too much time trying to understand what the VB6 developer meant to do.

Please notice that this implementation of VarPtr works well in 32-bit applications only, and fails when running on 64-bit operating systems. To ensure that things work as expected and that 32-bit code is generated even when running on 64-bit versions of Windows, you must select the Target CPU = x86 option in the Advanced Compile Options dialog box, in the Compile tab of the My Project page. (Odds are that you have to select this option anyway when doing complex operations with pointers.)

Important warnings: working with memory addresses under .NET can be very dangerous, much more dangerous than under VB6. The reason is, the garbage collector can fire virtually anytime while the program is executing, therefore the address of an object can suddenly change and the unmanaged method (CopyMemory in above example) would receive the address of a memory area that doesn't contain the data any longer. The neat result would be either a wrong value or an application crash. When using this implementation of VarPtr under VB.NET keep the following points into account:

  1. VarPtr is absolutely safe only when used to return the address of simple local variables, such as Short, Integer, Single, Double, or Date variables. Using local variables is safe because local variables are allocated on the stack and don't move even if an unexpected garbage collection occurs immediately after the VarPtr method returns but before the unmanaged method complete its execution.
  2. Passing the element of a Structure is safe, but only if the Structure is held in a local variable (as opposed to a class field)
  3. In all other cases, VarPtr isn't  100% safe and might occasionally deliver wrong results or crash the application. For example, it isn't 100% safe to pass VarPtr a class field or an element of an array, because array elements are stored in the managed heap and can be moved by the garbage collector (regardless of whether the array is stored in a local variable). 
  4. In a single-thread application the probability that a garbage collection occurs unexpectedly are very low and might even be considered as negligible, but they can't be considered as equal to zero.
  5. You can further minimize the probability of an unexpected GC by avoiding calling methods and language functions (e.g. Left, Int, Abs) inside the call to the unamanged method but, again, you can't reduce this probability to zero.

To recap, except when you are in cases #1 and #2 above, the converted code will work most of the time, but it can't be guaranteed to work always.The only documented way to ensure that an object doesn't move in memory because of unexpected gargabe collection is by pinning the object, by means of methods exposed by the System.Runtime.InteropServices.Marshal class.

Even with this limitation, the VB.NET implementation VarPtr method is quite helpful when doing a quick-and-dirty migration - using VB Migration Partner or the Upgrade Wizard. You can use VarPtr to check that the converted code works as intended, but it is strongly recommended that you get rid of VarPtr before going to production. In the CopyMemory case see above, for example, you can do without the VarPtr by using a different overload of the CopyMemory method that takes by-reference arguments:

 

Declare Sub CopyMemoryByref Lib "Kernel32.dll" Alias "RtlMoveMemory" _
   (ByRef dest As Integer, ByRef source As Integer, _
    ByVal numBytes As Integer)

(This code works because the .NET Framework automatically pins every object passed by reference to an external method.)

Even better, you should do your best to avoid unamanged calls altogether and replace them with calls to methods of pure .NET objects.



More on binary compatibility

clock December 20, 2008 06:11

A reader of a previous post on binary compatibility asked for clarification about my claim about VB Migration Partner being the the only conversion tool on the market offering this important feature, mentioning that at least another tool from our competitors also claim to offer this feature, which they obtain by adding ComVisible, ProgID, and ClassInterface attributes.

I am aware there is some margin for ambiguity and for uncorrect information, so I decided to drill a bit deeper on this topic.

You have binary compatibility between a COM component a .NET component if you can unregister the COM component and install the .NET component in its place, without having to recompile the COM clients that were using the COM component. We call it "binary compatibility" because it's the kind of compatibility we used to enforce under VB6, to ensure that a new version of a given component was perfectly compatible with its previous releases.

The ability to generate a VB.NET component that is binary-compatible with the original VB6 component is greatly useful, for many reasons:

  • You can migrate a complex N-tier application starting with its innermost DLLs, typically those that access the database, without having to recompile or modify in any way existing COM clients. 
  • The ability to convert only a subset of existing DLLs without being forced to migrate the entire application allows you to focus on those components that suffer most from the inherent scalability and performance limitations imposed by COM. This is the so-called phased migration.
  • Just as important, you can immediately debug the converted DLLs because you can use existing COM clients and their user interface - no need to write small throw-away .NET projects with the only purpose of testing the DLLs you just converted.
  • Finally, if your organization is very serious about testing, odds are that you have defined unit tests, use cases, and maybe scripts that rely on commercial testing tools. The great news is that, in most cases, binary compatibility allows you to test the converted DLLs using existing unit tests and scripts, thus saving you even more time.

As you see, binary compatibility can be a tremendous time-saver when migrating a VB6 application that is split in many DLLs, and it's no surprise that a migration tool vendor would like to claim that its product offers this feature.

Unfortunately for our competitors, to generate a .NET class that is binary-compatible with the original VB6 class you need much more than three attributes. You have to extract GUIDs from the original DLL and apply them correctly to the generated .NET class, you must handle fields, enums, types, events, and interfaces with special care, and you have to pay attention to many special cases and exceptions.

More precisely if you just add the ComVisible, ProgID, and ClassInterface attributes, the best you can get is compatibility with existing VBScript and ASP clients, not with compiled COM clients 

The bottom line: based on what I can read and what I heard from customers, I can only reinforce my claim that only VB Migration Partner offers true backward binary-compatibility with existing COM clients and fully supports phased migrations of N-tier VB6 apps.



Exploring version 1.10 - Type inference

clock November 23, 2008 04:19

VB Migration Partner 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:

'## DeclareImplicitVariables True

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
        ' UPGRADE_WARNING (#0364): Unable to assign default member of 'Width'.
        ' Consider using the SetDefaultMember6 helper method.

        Return m_Width
     End Get
    Set(ByVal value As Object)
        ' UPGRADE_WARNING (#0354): Unable to read default member of 'value'.
        ' Consider using the GetDefaultMember6 helper method.

        m_Width = value
     End Set
End Property

Public Sub Test(ByRef x As Short, ByRef frm As Object)
    ' UPGRADE_INFO (#05B1): The 'res' variable wasn't declared explicitly.
    Dim res As Object = Nothing
    ' UPGRADE_INFO (#0561): The 'frm' symbol was defined without an
    ' explicit "As" clause.

    Dim v1 As Object = Nothing
    ' UPGRADE_INFO (#0561): The 'v2' symbol was defined without an
    ' explicit "As" clause.

    Dim v2 As Object = Nothing
    ' UPGRADE_WARNING (#0364): Unable to assign default member of 'v1'.
    ' Consider using the SetDefaultMember6 helper method.
    v1 = x * 10
    ' UPGRADE_WARNING (#0364): Unable to assign default member of 'v2'.
    ' Consider using the SetDefaultMember6 helper method.

    v2 = Width + x
    frm = New Form1()
    ' UPGRADE_WARNING (#0354): Unable to read default member of
    ' 'frm.Caption'. Consider using the GetDefaultMember6 helper method.
    ' UPGRADE_WARNING (#0364): Unable to assign default member of 'res'.
    ' Consider using the SetDefaultMember6 helper method.

    res = frm.Caption
End Sub

Variant variables – either implicitly declared or not – are translated into Object variables, which causes several warning to be emitted and, more important, adds an overhead at runtime. This overhead can be often avoided by declaring the variable of a more defined scalar type. You can have VB Migration Partner change the data type for a given member by means of the SetType pragma, but this operation requires a careful analysis of which values are assigned to the variable.

Users of VB Migration Partner 1.10 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:

'## InferType Yes

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)
    ' UPGRADE_INFO (#0561): The 'frm' symbol was defined without an
    ' explicit "As" clause.
    ' UPGRADE_INFO (#05B1): The 'res' variable wasn't declared explicitly.

    Dim res As Object = Nothing
    Dim v1 As Object = Nothing
    ' UPGRADE_INFO (#0561): The 'v2' symbol was defined without an
    ' explicit "As" clause.

    Dim v2 As Single
    ' UPGRADE_WARNING (#0364): Unable to assign default member of 'v1'.
    ' Consider using the SetDefaultMember6 helper method.

    v1 = x * 10
    v2 = Height + x
    frm = New Form1()
    ' UPGRADE_WARNING (#0354): Unable to read default member of
    ' 'frm.Caption'. Consider using the GetDefaultMember6 helper method.
    ' UPGRADE_WARNING (#0364): Unable to assign default member of 'res'.
    ' Consider using the SetDefaultMember6 helper method.

    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:

'## InferType Force, True

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)
    ' UPGRADE_INFO (#0561): The 'frm' symbol was defined without an
    ' explicit "As" clause.
    ' UPGRADE_INFO (#05B1): The 'res' variable wasn't declared explicitly.

    Dim res As String = Nothing
    Dim v1 As Integer
    ' UPGRADE_INFO (#0561): The 'v2' symbol was defined without an
    ' explicit "As" clause.

    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. Deducing the correct type of an object often produces faster and more readable code. For example, consider the following VB6 code:

'## InferType
Public Sub SetAddress(ByVal address As String)
    Dim tb
    Set tb = Me.txtAddress  ' (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. 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.



Exploring version 1.10 - Unused/unreachable code detection and removal

clock November 21, 2008 01:23

It’s no secret that a typical business application contains a lot of dead code that is never executed. This is especially true if the application has evolved over many years and has been authored by many developers. The percentage of useless code can vary, but is typical in the range 5% to 10%.

In a VB6 application this dead code consumes a little memory but doesn’t otherwise hurt the overall performance. However, things are quite different if you want to port that application to VB.NET – either by hand or automatically by means of a conversion tool.

In fact, if you have no way to spot where this dead code is, you might be wasting 5-10% of your time and money just to migrate code that will be never executed anyway. Considering how much migration initiatives may cost, this small percentage becomes a very high number in absolute terms.

VB Migration Partner has always included a very sophisticated code analysis engine that automatically spots portions of code that are unreachable – for example, the code that immediately follows an Exit Sub statement and that doesn’t begin with a label – as well as methods that aren’t referenced by code elsewhere in the same project or project group. VB Migration Partner distinguishes the two cases by means of different warning messages:

' UPGRADE_INFO (#0521): Unreachable code detected
' UPGRADE_INFO (#0501): The ‘XYZ’ member isn’t used anywhere in current application

The code analysis engine is smart enough to detect whether two methods call each other but not from the main application, or fields that are accessed only by methods that, in turn, are never invoked by other code. In this case the message is slightly different:

' UPGRADE_INFO (#0511): The ‘XYZ’ member is used only by members that
' haven’t found to be used in current application

Keep in mind that you can selectively disable warning messages by means of the DisableMessage pragma, as in:
    '## DisableMessage 521
or you can suppress all messages related to code analysis by means of the DisableMessages pragma:
    '## DisableMessages CodeAnalysis

Like all code analysis tools, VB Migration Partner may issue warnings related to members that appear to be unused only because the tool is analyzing one project at a time. For example, if you convert an N-tier application one component at the time, an analysis tool flags the majority of public members as “unused” even though they are used by client apps that haven’t been included in the code analysis. If the application isn’t too large, you can solve this issue by analyzing a project group that gathers all the individual projects, but this workaround isn’t practical for applications of dozens or hundreds different projects.

Another problem related to correctly spotting unused members is late-binding: if a given method or property is accessed by client code in late-bound mode, no code analysis tool is capable to detect that the member is actually used and shouldn’t be removed.

VB Migration Partner offers a “manual” solution to these problems in the form of two pragmas: the MarkAsReference pragma tells the code engine that a given member is actually referenced – for example, via late binding – and therefore the code analysis engine should omit warnings #0501 and #0511.

The MarkPublicAsReferenced pragma is useful when analyzing and migrating ActiveX DLL and OCX projects: when this pragma is used, all public properties and methods are marked as referenced. In turn, if these public members reference private variables or methods, these private members will be marked as referenced, too. The MarkPublicAsReferenced pragma can be scoped at the project or file level. For example, when migrating an ActiveX project, you typically need one single pragma:

    '## project:MarkPublicAsReferenced

All the features discussed so far have been available in all editions of VB Migration Partner since version 1.0. The forthcoming version 1.10 goes further, because it offers the ability to automatically remove unreachable code and unused members.

In version 1.10 you can remove unreachable code by means of the RemoveUnreachableCode pragma, which takes one of the following arguments: On (or omitted, remove the unreachable code), Off (don’t remove the unreachable code), Remarks (comment the unreachable code). For example, let’s consider this VB6 code:

Sub Test()
    ' GoSub DisplayMessage
    ' …
    ' more code here …
    ' …

    Exit Sub
DisplayMessage:
    MsgBox "Inside Test method"
    Return
End Sub


The code that begins with the DisplayMessage label was originally the target of the GoSub statement; it is clear that the developer later remarked out the Gosub keyword but forgot to delete the MsgBox and Return statements. This is how VB Migration Partner migrates this code:

Public Sub Test()
    ' GoSub DisplayMessage
    ' …
    ' more code here …
    ' …

    Exit Sub

    ' UPGRADE_INFO (#0521): Unreachable code detected

DoSomething:
    MsgBox6("Inside Test method")
    ' UPGRADE_INFO (#05A1): The VB6 code contains a Return but no
    ' GoSub has been found in current method.

    Err.Raise(3)
End Sub


Before deleting unreachable code, you might want to check that the program works correctly, by just commenting out the code, by adding the following pragma:

'## RemoveUnreachableCode Remarks
Public Sub Test()
    ...


This is what you get now:

Public Sub Test()
    ' GoSub DisplayMessage
    ' …
    ' more code here …
    ' …

    Exit Sub

    ' UPGRADE_INFO (#06F1): Unreachable code removed
    ' EXCLUDED: DoSomething:
    ' EXCLUDED: Beep()
    ' EXCLUDED: Err.Raise(3)

End Sub

Not that you are sure that the program works well, you can use the RemoveUnreachableCode pragma with no arguments:

'## RemoveUnreachableCode


which produces this code:

Public Sub Test()
    ' GoSub DisplayMessage
    ' …
    ' more code here …
    ' …

    Exit Sub

    ' UPGRADE_INFO (#06F1): Unreachable code removed

End Sub


Notice that the above code contains a new warning, which alerts you on the fact that some code has been removed. As usual, you can get rid of this new warning by means of a suitable DisableMessage pragma.

If you like the idea of automatically removing unreachable portions of code, you’ll surely appreciate the capability of doing the same with whole unused members, that is all the members that would be flagged with either the #0501 or #0511 warnings. In this case you need a RemoveUnusedMembers pragma, whose argument can be On (or omitted), Off, and Remarks – exactly like the RemoveUnreachableCode pragma seen above.
As before, you might want to remark out unused members first, to see how the code works

    '## RemoveUnusedMembers Remarks

and then delete them completely with
 
    '## RemoveUnusedMembers

When removing variables and methods, VB Migration Partner inserts a warning in the migrated code:

' UPGRADE_INFO (#0701): The 'XYZ' member has been removed because it
' isn't used anywhere in current application.

When running the tool to get the definitive VB.NET version of your code, it’s a good idea to get rid of this message with

    '## DisableMessage 0701


One final note: The ability to selectively indicate which members are to be considered as referenced makes VB Migration Partner stand out among other analysis/migration tool that don’t offer the ability to remove dead code or that allow you to enable this feature only at the project-level.

In fact, when migrating large and complex N-tier application, you can rarely remove all unreferenced members, because they might be referenced in client apps that aren’t part of the same project group. In practice, if a tool doesn’t give you the option to selectively decide which members should be removed, this feature is rarely usable when migrating large applications.



Exploring version 1.10 - If merging

clock November 20, 2008 00:51

If merging is VB Migration Partner's ability to merge two or more nested If...End If blocks using the AndAlso operator. This feature is enabled by means of the MergeIfs  pragma. To see how this feature works, consider the following VB6 code:

'## MergeIfs True
Sub Test(ByVal x As Integer, y As Integer, z As Integer)
    If x > 0 Then
        If y = 0 Then
            ' do something here
            ' …

            If z <> 0 Then
                If x < 100 Then
                    ' do something here
                    ' …

                End If
            End If
        End If

        If z = 0 Then If x = 10 Then Beep
    End If
End Sub

This is the VB.NET code that VB Migration Partner produces:

Public Sub TestX(ByVal x As Short, ByRef y As Short, ByRef z As Short)
    ' UPGRADE_INFO (#0581): Two nested If...End If blocks have been merged.
    If (x > 0) AndAlso (y = 0) Then
        ' do something here
        ' ...

       
        ' UPGRADE_INFO (#0581): Two nested If...End If blocks have been
        ' merged.

        If (z <> 0) AndAlso (x < 100) Then
            ' do something here
            ' ...

        End If

        ' UPGRADE_INFO (#0581): Two nested If...End If blocks have been
        ' merged.
         If (z = 0) AndAlso (x = 10) Then  Beep()
    End If
End Sub


The effect of this pragma is to simplify the structure of the code, reduce nesting level, and indirectly make it more readable. As you see, the MergeIfs pragma also affects nested single-line Ifs. A warning is automatically added, so that you can quickly revise all points where this optimization was applied.

By default, test expressions are enclosed between parenthesis, so that you easily see how each If statement contributed to the compound expression. If you don’t like these extra parenthesis, just specify False as the pragmas’s second argument:

    '## MergeIfs True, False

The MergeIfs pragma can have project-, file-, and method-level scope, therefore you can precisely define where this optimization should be applied. In most cases you can safely specify project-level If merging by including the following line in the VBMigrationPartner.pragmas file:

    '## project:MergeIfs True



Speed up your VB.NET collections

clock November 8, 2008 04:24

The VB6 Collection is quite a versatile object: it allows you to add and remove elements by their index like in an array, but without requiring to initialize it to a fixed number of elements. It also allows you to associate a string key to an element, so that you can quickly retrieve (or delete) that element without having to scan the collection one element at a time. (An important detail: string keys are compared in case-insensitive mode.)

Both the Upgrade Wizard and VB Migration Partner convert VB6 Collection objects into the Microsoft.VisualBasic.Collection objects, a choice that ensure the highest degree of compatibility and functional equivalence. A few articles around the Internet suggest that you should try to use a "pure" .NET collection, if possible. 

Unfortunately, this is easier said that done, because no other .NET collection exactly mimicks the behavior of the VB6 Collection. If you never use string keys you can translate a VB6 Collection using the ArrayList or the List(Of T) objects, except these objects have their lower index equal to 0 instead of 1, a detail that would force you to carefully scrutinize and fix your code as necessary. On the other hand, a VB6 collection that is only used with string key might be converted to VB.NET using the HashTable or the Dictionary(Of T), but be careful because these .NET collections deal with string key in case-sensitive mode and therefore aren't perfectly equivalent to the original VB6 Collection object. (TIP: you can use the CollectionUtils.CreateCaseInsensitiveHashtable method to create a case-insensitive dictionary.)

The main reason for replacing the VB.NET Collection with a native .NET collection is performance. The following table recaps the timing (in milliseconds) to perform a given operation 10,000 times. As you see, while the VB.NET collection is marginally faster than the VB6 collection in most cases, it is also much slower than the ArrayList collection at retrieving and deleting elements. (It is also slower than the Hashtable object, but the difference in absolute terms is virtually negligible.)

Test  VB6   VB.NET   ArrayList   Hashtable   VB6Collection  
Add(item) 0 1 0 n/a 2
Add(item,key) 47 7 n/a 4 6
Item(ndx) 203 215 0 n/a 0
Item(key) 16 7 n/a 0 9
For Each 15 7 0 0 1
Remove(ndx) 609 539 46 n/a 247
Remove(ndx) (*)    140 131 46 n/a 70
Remove(key) 16 12 n/a 3 3820
  (*) if items aren't associated with keys

 

The table suggests that, if you are sure that you never use keys, then you should replace a collection with an ArrayList object. Likewise, if you are sure that the collection is always going to contain elements of a specific type - for example, strings - you can improve performance and code robustness by replacing the collection with a List(Of T) object. However, if your code uses all the features of the VB6 Collection - namely, numeric indexes and string keys, you apparently have no choice other than using the standard VB.NET collection, right?

Well, not if you use VB Migration Partner. Starting with forthcoming version 1.10, you can replace a VB.NET collection with the new VB6Collection object, provided in our support library. As shown in previous table, this custom object is remarkably faster than the VB.NET collection in all tests (except when passing a key to the Remove method), and is as fast as the ArrayList object at retrieving elements by their numeric index.

When version 1.10 is released, you can replace one or more instances of the VB.NET Collection with our custom VB6Collection type by means of a ReplaceStatement pragma:

    '## ReplaceStatement Dim col As New VB6Collection
    Dim col As New Collection

Even better: we provide also a generic, strong-typed version of this collection, named VB6Collection(Of T). If you are sure that the collection is going to contain only element of a certain kind - integers, for example - you can write better code by using the generic version of this collection:

    '## ReplaceStatement Dim col As New VB6Collection(Of Integer)
    Dim col As New Collection

The VB6Collection class is 100% compatible with the original VB6 collection and the VB.NET collection, therefore you don't need to edit the VB.NET source code in any other way.