11 false myths about support libraries

clock October 31, 2009 05:07

Some developers may be concerned about support library and would prefer not to distribute an additional DLL with their migrated apps. This is understandable, but we believe that these concerns are negligible if compared to the many advantages that a well-written, comprehensive library gives you.

Such benefits stand out so clearly that even our competitors can’t deny that an extensive library can speed up a migration project. To counter this evidence, however, they try to convince that a library has many shortcomings. Fortunately, we can easily prove that all these “issues” are groundless, as we do in our 17 reasons for using a support library in migration scenarios whitepaper.

The point is, a comprehensive support library is the key factor in achieving 100% functional equivalence and in keeping migration time and cost as low as possible. Just read what one of our customers has to say:

“An initial migration compared migration tools from six vendors. It showed superior results for VB Migration Partner, which delivered fewer compilation and runtime errors than all its competitors… It took 2.5 hours to get a compilable and runnable VB.NET project with VB Migration Partner, and 13 hours with its closest competitor.”

The higher productivity that a complete support library gives you is a sure fact, and our competitors haven’t found yet a way to obtain different results in similar rigorous productivity tests. In the absence of objective measurements, they attempt to scare potential customers away from VB Migration Partner by making vague statements about its supposed shortcomings.

These statements never mention VB Migration Partner or Code Architects, yet the target is undoubtedly our product because we are the only VB6 conversion software vendors who use an extensive support library:

1)    …other vendors charge a runtime fee for their support library.

FALSE! VB Migration Partner users can freely distribute its library with their apps.

2)    …other vendors don’t make their library’s source code available to customers.

FALSE! VB Migration Partner users can license the library’s code.

3)    … apps that rely on a support library might not work on future versions of the operating system.

FALSE! VB Migration Partner’s library is written in pure VB.NET and uses only documented features of the .NET Framework. As such, the library is guaranteed to work on all future Windows versions that support .NET Framework 3.5 binaries.

A closer examination reveals that this line of reasoning is illogical. Just for the sake of discussion, let’s assume that Microsoft releases a Windows or .NET Framework version breaks compatibility with existing .NET 3.5 programs. In such a highly unlikely case, your own VB.NET applications will stop working correctly and the support library will be the least of your problems.

A support library actually helps to preserve compatibility. Should a future version of Windows have minor problems in running .NET 3.5 code, Code Architects will promptly fix the problem and release a new support library that all users can download for free. You’ll still have to solve compatibility problems of your main app, but at least you don’t have to worry about the support library.

4)    …other vendors may charge a subscription fee for using the library in the future.

FALSE! VB Migration Partner’s EULA states that users will be allowed to download any future 1.xx version of the library at no additional cost. Future 1.xx releases include fixes for all the problems that might be discovered in the future by Code Architects or its customers.

5)    … if your migrated apps depend on a 3rd-party library, you might be in trouble if the vendor decides to stop supporting it in the future

FALSE! VB Migration Partner’s EULA states that – should Code Architects stop supporting the product – all customers will receive the library’s source code at no additional cost. In this respect, choosing Code Architects is as safe as, or safer than, choosing any 3rd-party control, such as the grid or the report component that you might be using in most of your forms.

6)    … a support library can slow down your code

FALSE! VB Migration Partner’s library is carefully optimized and often runs much faster than the code that most VB.NET or C# developers usually write, especially if they are put under pressure by near deadlines. For example, our VB6Collection class runs many times faster than the standard VB.NET Collection object, and our StringBuilder object allows you to automatically speed up string concatenations by a factor of 100x without having you modify the generated VB.NET code.

Keep in mind that VB Migration Partner’s library has been authored by a team of expert developers, including two Microsoft Regional Directors who have written 7 top-selling Microsoft Press books on VB and .NET programming, routinely give lectures in US and Europe, and consult for Microsoft and its largest customers.

7)    …other migration tools generate inefficient code that retains its VB6 flavor

FALSE! VB Migration Partner’s conversion engine uses very sophisticated refactoring techniques and generates code that takes full advantage of VB.NET features, including Try/Catch blocks, short-circuiting (the AndAlso operator), the IDisposable interface, variable initializes, compound operators (e.g. x += 1), and much more.

Interestingly, VB Migration Partner can apply many refactoring techniques that no other VB6 conversion tool currently supports, e.g. Gosub refactoring, Declare overloads, faster string concatenations inside loops, enforcement of ByVal keyword if possible. Our competitors should think twice before drawing developers’ attention to this point.

Finally, consider that VB Migration Partner’s developers wrote Practical Guidelines and Best Practices for VB and C# Developers (Microsoft Press), perhaps the definitive textbook on this topic, and VB Migration Partner’s generates code that abides to all those rules. Additionally, VB Migration Partner is able to rename members to comply with .NET coding guidelines. If you don’t like standard .NET rules you can easily customize the rename engine by modifying an XML file. No other conversion software supports customizable rename rules.

8)    …the code produced by other VB6 converters that adopt a support library is hard to maintain

FALSE! A support library can make your migrated code more readable and easier to maintain, as I demonstrated in a previous post.

9)    …projects produced by other VB6 converters are difficult to evolve

FALSE! VB Migration Partner library doesn’t limit you in any way, because all its classes inherit from native .NET classes and you can therefore leverage the .NET Framework full potential.

10)    …you can’t mix .NET native forms and controls with forms and controls defined in a support library

FALSE! After you’ve converted a VB6 project to VB.NET using VB Migration Partner, you can extend the application by adding .NET native forms, and you can drop .NET native controls onto a migrated form. The one thing you can’t do is using a control defined in our support library in a standard .NET form, an action that would be meaningless anyway.

11)    …you can’t use Visual Studio 2008 Test Wizard to generate unit tests for VB.NET projects that use a support library.

FALSE! We describe this apparent problem – and its solution - in this KB article.



Migration pragmas vs. project-wide customization settings

clock October 28, 2009 05:38

VB Migration Partner's pragmas offer a powerful and granular approach to code customization. With 70+ pragmas available, you can control virtually any single aspect of code conversion. For example you can use pragmas to:

•    correctly convert Null and Variant values
•    solve problems caused by arrays with nonzero lower bound, As New (auto-instancing) variables, IDisposable variables, etc.
•    optimize code that would be inefficient when converted as-is to .NET (e.g. string concatenation)
•    prevent subtle runtime exceptions caused by imperfect functional equivalence between VB6 and VB.NET
•    remove or comment unused or unreachable code, use Try-Catch blocks where possible, refactor Gosub keywords into calls to separate methods, and so forth
•   ... and much more.

Each pragma is described in depth in our online documentation, therefore I won't explain what each pragma does. Suffice it to say that all these pragmas allow you be in control of VB Migration Partner's code generation engine, so that you can always generate the most efficient and robust VB.NET code that is functional equivalent to the original VB6 project.

The purpose of this article is to compare migration pragmas with the customization mechanisms offered by other VB6 conversion tools.

In a nutshell, a migration pragma is a remark that you insert in the original VB6 code base or in a separate file named <projectname>.pragmas. Pragma files are very handy for copying groups of pragmas to another migration project, on the same or different computer. Pragma files even support #Include directives, therefore different projects can actually share a centralized pragma file. If you later want to add or remove a pragma from the centralized file you don't have to manually edit individual files.

The great thing about pragmas is that they can affect the entire solution/project, a single file, or an individual method or variable. This sophisticated scoping mechanism allows you to define a general conversion rule at the project-level and then mention one or more exceptions at the file-, method-, or variable-level.

VB Migration Partner is the only conversion software that supports pragmas. All other conversion tools offer personalization rules that can only work at the project level. Each rule is an all-or-nothing decision, and you can't apply a rule only to a portion of the project. As you'll see shortly, such lack of granularity can be a problem in real-world scenarios.

Let’s see a concrete example that shows how the two approaches compare to each other.

VB6 language supports Variant variables, which can contain both a scalar value - e.g. a number or a string - or a reference to an object. When migrating to VB.NET such Variant variables, all migration tools convert those Variant variables as Object variables. For example, consider the followingVB6 code:

   Sub ShowDefaultMemberValue(ByVal var As Variant)
      Dim s As String
      s = var
     MsgBox(s)
   End Sub

This method works well both when var contains a scalar value and when it contains a reference to an object, for example a TextBox control. In the latter case, the method displays the default member for the object (e.g. the Text property if var points to a TextBox control, or the Value property if var points to an ADODB.Field object.)

By default, VB Migration Partner and other conversion tools convert the above method to VB.NET as follows:

   Sub ShowDefaultMemberValue(ByVal var As Object)
      Dim s As String = var
      MsgBox(s)
   End Sub

This code works well if var contains a scalar value, but throws an InvalidCast exception if var points to an object. The reason is that the VB.NET is unable to extract the default member of an object, unlike VB6.

NOTE: VB Migration Partner and possibly other products can, in some cases, use type inference to convert these Variants into definite types such as Integer or String. Besides, VB Migration Partner is also able to use the special VB6Variant variables, for improved functional equivalence. We won't take these cases into account, because aren't important for our general discussion on pragmas.)

Let's now see how each different conversion tools can fix this problem.

VB Migration Partner supports the DefaultMemberSupport pragma, which tells the code generation engine that references to Variant and Object variables should be wrapped in a helper method that determines the object’s default member and returns its value to callers:

   Sub ShowDefaultMemberValue(ByVal var As Object)
      Dim s As String = GetDefaultMember6(var)
      MsgBox(s)
   End Sub


The GetDefaultMember6 method (defined in VB Migration Partner's support library) returns the value of the Text property for a TextBox control, the value of the Checked property for a CheckBox or OptionButton control, and so on. A similar helper method named SetDefaultMember6 is used to assign the default member of a given object that is accessed in late-bound mode.

The GetDefaultMember6 and SetDefaultMember6 methods clearly improve functional equivalence with the original VB6 code at the expense of reduced code readability and maintainability. In addition, these two methods can slightly affect runtime performance.

For these reasons, we recommend that you use the DefaultMemberSupport pragma only where one or more variables are known to contain a reference to a control or object. Conversely, if an Object variable is the result of the conversion of a VB6 Variant that can contain a scalar value, you should avoid the overhead – in both readability and performance – introduced by these methods.

Avoiding such an overhead is easy with VB Migration Partner, because its pragmas can be granularly scoped at the file or method level, and even at the single variable level. In previous example, you can leverage this granularity and associate the DefaultMemberSupport pragma only with the obj variable:

    '## obj.DefaultMemberSupport True

When such a pragma is used, only references to the obj variable are wrapped in GetDefaultMember6 or SetDefaultMember6 methods. (One pragma affects all occurrences o a given variable, or all variables defined in a given method or file.) All other Object variables are unaffected by this specific pragmas.

Conversely, because of their project-wide conversion rules, when using a converter other VB Migration Partner you're forced to pollute your entire code base with one helper method call for each occurrence of an Object variable, even if that variable can only contain a scalar value. Such a massive presence of helper method calls can have a serious impact on overall performance, readability, and ease of maintance

Not surprisingly, given these drawbacks, users of products other than VB Migration Partner often avoid enabling this and other project-level options, for example support for IsMissing keywod, conversion from On Error to Try/Catch, and improved support for late-bound calls.

This is a well-known problem that only VB Migration Partner's pragmas can solve in a simple and effective way.



[NEW VERSION] VB Migration Partner 1.21 has been released

clock October 23, 2009 09:03

We have just made VB Migratio Partner 1.21 available to all registered users.

The new version fixes a few minor bugs and adds some interesting features:

  • the TabEnable and TabVisible properties of the SSTab control
  • hotkeys in SSTab captions, and the ability to change the alignment of SSTab captions by means of the new TabCaptionAlignment property, so that you can perfectly emulate VB6 appearance if you wish
  • the new Collection6 class can optionally replace a plain Collection object if you want to preserve the VB6 semantics when adding arrays to a VB.NET collection
  • the code generator generates the correct code for Erase keywords that work on static arrays
In addition to these technical detais, we wanted to improve the user experience and reduce the need for our customers to contact our tech support. The first time you convert a VB6 project, VB Migration Partner 1.21 displays a message box that invites to generate a report for all the migration warnings and issues in the converted VB.NET project. This simple trick will help first-time users to correctly decode all migration messages and apply the right pragma to fix all most common issues.

As usual, registered customers will be alerted that there is a new version available the next time they run VB Migration Partner (provided that they have a working Internet connection, of course).



Static and dynamic arrays ARE different, after all

clock October 23, 2009 08:43
In VB6 you have two different kinds of arrays, static and dynamic. A VB6 static array is defined by means of a DIM keyword that specifies lower and upper indexes, whereas a dynamic array is defined by means of a DIM keyword with empty parenthesis

    Dim arr1(10) As Integer     ' a static array
    Dim arr2() As Integer       ' a dynamic array

The key difference between static and dynamic arrays is that you can't change the size of a static array. VB.NET supports both syntax forms, but in all cases it creates dynamic arrays.

However, there is another, less obvious difference between static and dynamic arrays under VB6, which becomes apparent when you apply the Erase keyword to the array. When you erase a static array all the array elements are reset to zero, empty string, or nothing; when you erase a dynamic array all items are destroyed and you can't access any element until you REDIM-ension the array.

Previous versions of VB Migration Partner didn't account for this minor detail, which is also ignored by all other VB6 conversion tools on the market. In upcoming 1.21 version, VB Migration Partner generates a slightly different code when the original VB6 array was static:

    ClearArray6(arr1)   ' a static array
    Erase6(arr2)        ' a dynamic array

Unfortunately, it isn't possible to fill the gap between VB6 and VB.NET in all cases. In fact, if the array is one of the parameters of the current method, VB Migration Partner has no way to determine whether the client is passing a static or a dynamic array, because the same method can be passed arrays of both types. For this reason, it assumes that the array is dynamic and uses the Erase6 method, but it additionally generates a warning to alert the developer of the potential problem. 



How support library REALLY affects long term code maintainability

clock October 23, 2009 04:05

VB6 and VB.NET are similar languages that differ for a myriad of major and minor details. Even keywords, methods, and controls that have the same name in both environments may have a completely different behavior, as we exhaustively show in our Resource section.

A tool that aims at preserving functional equivalence with the original VB6 code should account for all or at least the majority of these differences. All the VB6 conversion products on the market, including VB Migration Partner, fill this gap with a combination of these two elements:

  • code transformation techniques, to generate VB.NET code that behaves like the original VB6 code
  • support library that expose methods and controls that aren't found in the .NET Framework or in the Microsoft.VisualBasic namespace

Even the Upgrade Wizard included in Visual Studio, arguably the least sophisticated conversion tool around, relies on TWO support libraries - Microsoft.VisualBasic.Compatibility.dll and Microsoft.VisualBasic.Compatibility.Data.dll - to support VB6 features that have no direct correspondence in VB.NET, such as control arrays and the ADODC, DriveListBox, DirListBox, and FileListBox controls.

One of VB Migration Partner's strengths is its comprehensive support library. Its dozens of classes and hundreds of methods ensure that the generated VB.NET code always performs like the original VB6 app, an important factor in dramatically reducing the time and cost of the migration process. No other conversion tool comes with such a complete library and in fact no other conversion tool can compare to VB Migration Partner in its support for VB6 features.

Our competitors are aware of the many advantages of this approach, therefore some of them are adopting a twofold strategy: on one hand they are expanding *their* support library, on the other they publish strongly biased whitepapers where they claim that that extensive libraries compromise the maintainability of the generated VB.NET project.

Notice that VB Migration Partner is never explicitly mentioned in these documents, therefore authors don't feel compelled to back up their claims with any sort of evidence.

In this article I'll focus only on the supposed lack of readability and maintainability of converted VB.NET that use a support library. But unlike our competitors, I'll build my assertions on actual code snippets and compare the code that VB Migration Partner generates with the result from a “traditional” VB6 conversion tool that comes with a less powerful support library. Consider the following KeyPress handler written in VB6:

Private Sub Text1_KeyPress(KeyAscii As Integer)
   ' convert the pressed key to uppercase, but ignore spaces
   If KeyAscii = 32 Then KeyAscii = 0 : Exit Sub
   KeyAscii = Asc(Chr(KeyAscii))
End Sub

VB Migration Partner converts it as follows:

Private Sub Text1_KeyPress(ByRef KeyAscii As Short) Handles Text1.KeyPress
   ' convert the pressed key to uppercase, but ignore spaces
   If KeyAscii = 32 Then KeyAscii = 0 : Exit Sub
   KeyAscii = Asc(Chr(KeyAscii))
End Sub

The resulting VB.NET code is basically identical to the original code, and many VB6 developers understand how to read and maintain this code. Obviously there is no maintainability problem here.

Let’s see now the code produced by another conversion tool that doesn't rely on an extensive support library. The name the tool in question isn't really important, because the same concepts apply to any conversion tool that attempts to fill the gap between VB6 and VB.NET exclusively by means of code transformation techniques:

Private Sub Text1_KeyPress(ByVal eventSender As Object, _
      ByVal eventArgs As KeyPressEventArgs) Handles Text1.KeyPress
   Dim KeyAscii As Integer = Strings.Asc(eventArgs.KeyChar)
   ' convert the pressed key to uppercase, but ignore spaces
   If KeyAscii = 32 Then
      KeyAscii = 0
      If KeyAscii = 0 Then
         eventArgs.Handled = True
      End If
      Exit Sub
   End If

   KeyAscii = Strings.Asc(Strings.Chr(KeyAscii).ToString()(0))
   If KeyAscii = 0 Then
      eventArgs.Handled = True
   End If
   eventArgs.KeyChar = Convert.ToChar(KeyAscii)
End Sub

In the attempt to preserve functional equivalence of the original 3 statements inside the method, the tool generated as many as 13 (thirteen!!) statements. 

You might believe that the KeyPress event is a special and unique case, so let’s see another code snippet, a simple VB6 method that defines two optional parameters:

Public Sub TestOptional(Optional x As Variant, Optional y As Variant)
   If IsMissing(x) Then
      If IsMissing(y) Then
         x = 10
         y = 20
      End If
   End If
   MsgBox x * y
End Sub

This is how VB Migration Partner correctly translates it to VB.NET:

Public Sub TestOptional(ByRef Optional x As Object = MissingValue6, _
      ByRef Optional y As Object = MissingValue6)
   If IsMissing6(x) AndAlso IsMissing6(y) Then
      x = 10
      y = 20
   End If
   MsgBox6(x * y)
End Sub

Thanks to its support library, VB Migration Partner can generate code that is as readable and maintainable as the original VB6 method. Well, the generated code is actually more readable, because our software merged the two nested IFs, however this improvement is achieved by means of its sophisticated conversion engine and isn't a consequence of using a library.

Let’s now look at the code that our competitor's tool generates for the same method (for brevity's sake I removed 4 upgrade warnings):

Public Sub TestOptional(ByRef x_optional As Object, _
      ByRef y_optional As Object)
   Dim y As Object = Nothing
   If y_optional Is Nothing OrElse Not y_optional.Equals(Type.Missing) Then _
      y = TryCast(y_optional, Object)
   Dim x As Object = Nothing
   If x_optional Is Nothing OrElse Not x_optional.Equals(Type.Missing) Then _
      x = TryCast(x_optional, Object)

   Try
      If Not (x_optional Is Nothing) AndAlso _
         x_optional.Equals(Type.Missing) Then
         If Not (y_optional Is Nothing) AndAlso y_optional.Equals(Type.Missing) Then
            x = 10
            y = 20
         End If
      End If
      MessageBox.Show(CStr(CDbl(x) * CDbl(y)), Application.ProductName)
   Finally
      y_optional = y
      x_optional = x
   End Try
End Sub

Public Sub TestOptional(ByRef x_optional As Object)
   Dim tempRefParam As Object = Type.Missing
   TestOptional(x_optional, tempRefParam)
End Sub

Public Sub TestOptional()
   Dim tempRefParam2 As Object = Type.Missing
   Dim tempRefParam3 As Object = Type.Missing
   TestOptional(tempRefParam2, tempRefParam3)
End Sub

Now ask yourself: Which code would you like to maintain in the future? The concise adn readable 7-line method produced by VB Migration Partner or the 3 methods and 28 statements produced by the other tool?

Bottom line: never ever trust marketing literature that don't provide real-world examples and accurate comparisons.



A subtle collection behavior

clock October 21, 2009 17:09

Consider the following, apparently innocent piece of VB code:

' create and initialize an array
Dim arr(10) As Integer
arr(0) = 100
' store it into a collection
Dim col As New Collection
col.Add(arr)
' modify the array, compare with the array in the collection
arr(0) = 200
If arr(0) = col(1)(0) Then
    txtResult.Text = "Equal"
Else
    txtResult.Text = "Not equal"
End If

The question: what will be displayed in the txtResult control: "Equal" or "Not Equal?

Surprisingly, the correct answer is "It depends on the Visual Basic version you are using!" In fact, it displays "Not Equal" under VB6 and "Equal" under VB.NET.

In fact, when passing the array to the Collection.Add method, VB6 performs a copy of the array, therefore the subsequent assignment to arr(0) doesn't affect the copy already stored in the collection and the two arr(0) elements are now different. Vice versa, VB.NET passes a reference to the System.Array object, therefore there is only one array in memory and the assignment to arr(0) affects the same array as seen from the Collection.

This behavior can be the cause of a very subtle bug when converting a complex VB6 application to VB.NET, and neither Upgrade Wizard (included in Visual Studio) nor any VB6 conversion tool on the market can automatically solve this problem. The first time we bumped into this problem it took us hours to track it down, and we wanted to save our customers such headaches.

Once you see where the problem is, you can fix it by simply cloning the array being stored to the collection

        col.Add(arr.Clone())

VB Migration Partner also offers an alternative solution to this problem, in the form of the VB6Collection class, which offers this and several other enhancement over the Microsoft.VisualBasic.Collection object.



Interview on .NET Rocks

clock October 21, 2009 05:46

 

 

Yesterday .NET Rocks has finally published a long interview (63 minutes) with yours truly, about the migration of VB6 legacy apps and, of course, Code Architects' VB Migration Partner.

We touched many interesting topics and debased a few common myths about code migration. It was a great experience, thanks to Carl and Richar.

At this time no PDF transcript is available, but one should be added in a few days. In the meantime you'll have to keep up with my Italian accent, sorry Laughing



Software modernization company Transoft chooses VB Migration Partner

clock October 14, 2009 01:46

Transoft is a UK company that has a long tradition in software modernization. They offer both tools and services to migrate and modernize legacy apps written in Visual Basic, OpenVMS, IBM AS400, HP e3000, ICL VME, and others. They offer their products and services to both Europe and US and have an impressive list of achievements and customers of the caliber of DaimlerChrysler, DowChemical, and L'Oreal. 

Transoft is part of IRIS, a leading software company with over 1,000 employees worldwide. IRIS is the largest UK privately owned specialist software business with an exceptional reputation for delivering market leading solutions to more than 60,000 organizations, ranging from the micro to the multinational business and including major charities and membership organizations.

For all these reasons that we are VERY proud that, when looking for a software capable to automate most phases of the migration from VB6 and reduce migration time and cost, Transoft choosed Code Architects' VB Migration Partner over other similar and less powerful tools available on the market. 

If you are a US or UK company looking for a fast, cost-effective way to migrate and modernize your VB6 code, or just need support for your work with VB Migration Partner, you can contact Transoft or another of our partners that operate in your area.



[HOWTO] Fix ReDim statements that change the rank of an array

clock October 9, 2009 03:09

Not all developers know that VB6 permits to dynamically change the rank (i.e. the number of dimensions) of an array, as in this code snippet:

' this array can have 1 or 2 dimensions
Dim arr() As Integer
       
Sub InitArray(rank As Integer)
   Dim i As Integer, j As Integer
        
   If rank = 1 Then
      ReDim arr(100) As Integer
      For i = 1 To UBound(arr)
         arr(i) = i
      Next
   Else
      ReDim arr(100, 20)
      For i = 1 To UBound(arr, 1)
         For j = 1 To UBound(arr, 2)
            arr(i, j) = i * j
         Next
      Next
   End If
End Sub

VB.NET doesn’t allow you to change the rank of an array, therefore the converted VB.NET code would raise one instance of the following error:

Number of indices exceeds the number of dimensions in the indexed array

for each statement where the array is accessed with two indexes. These is no definitive solution to this problem, however VB Migration Partner allows you to significantly decrease the number of this compilation error, using a combination of pragmas:

    '## ParseReplace Dim arr() As Integer, arr2() As Integer
    Dim arr() As Integer
    '## arr2.ArrayRank 2
    '## PreProcess "arr(?=\([^,)]+,[^,)]+\))", "arr2"
    '## PreProcess "(?<=(LBound|UBound)\()arr(?=,)", "arr2"


An explanation of each pragma is in order. The ParseReplace pragma causes VB Migration Partner to realize that there are actually two arrays (arr and arr2) and the ArrayRank pragma specifies that arr2 is a 2-dimensional array. The first subsequent PreProcess pragma changes arr into arr2 if the array reference is followed by two indexes that are separated by a comma – as in arr(1,2)  - whereas the second PreProcess pragma changes array references that appear in LBound and UBound method calls. The result is this piece of error-free VB.NET code:

Private arr() As Short
Private arr2(,) As Short
    
Public Sub InitArray(ByRef rank As Short)
   Dim i As Short
   Dim j As Short
        
   If rank = 1 Then
      ReDim arr(100)
      For i = 1 To UBound6(arr)
         arr(i) = i
      Next
   Else
      ReDim arr2(100, 20)
      For i = 1 To UBound6(arr2, 1)     
         For j = 1 To UBound6(arr2, 2)
            arr2(i, j) = i * j
         Next
      Next
   End If
End Sub

This approach isn't bulletproof, though. In fact, it works only for array occurrences whose indexes aren't function calls. For example, it doesn't work in the following case:

      arr( GetIndex(1), GetIndex(2) )

However,  you can easily take care of these residual errors by means of additional ParseReplace or PreProcess pragmas.



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!