FRANCESCO BALENA ON VB MIGRATION

 

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.



Subtle threading issues and how to fix them (automatically) with VB Migration Partner

clock February 10, 2009 09:09

One customers brought an issue to our attention. He was using a migrated VB.NET component in a heavy multi-threaded environment (the component is called by ASP.NET pages) and found that occasionally the program crashed with an unexplainable error message. A quick look at the stack trace confirmed the initial suspect: the error was caused by two threads that initiates a call to the same method at the same time. We have fixed the problem in our library in a few minutes (the fix will appear in version 1.10.03, due in a few days), but we realized that the problem isn't confined to our library. To explain why, a short digression is in order.

VB6 components use Single-Threaded Apartment (STA) model: each thread lives in an “apartment” of its own. In plain English, this means that each thread works with a different set of global variables. For example, assume that the ActiveX DLL project contains a BAS module with the following variable declaration:

    ' ...(in the UserInfo.bas file)
    Public UserName As String


Thanks to STA threading model, each client – running in a different thread – can write this variable and read it back, without worrying about another client (in another thread) overwriting the variable with a different value. As you see, STA makes multi-threading programming virtually as easy as single-thread (i.e. traditional) programming. Of course,when writing multi-threaded apps you have to take care of many other potential concurrency issues – for example, two threads working on the same temporary file or registry key – but at least you don’t need to worry about global variables.

By contrast, components written in VB.NET – and all .NET languages, for that matter – use free-threading, which means that all threads can access all variables at the same time. Therefore, the VB.NET component exposes the same UserName field to all threads, which means that any thread can overwrite the value stored there by another thread.

To simplify programming in free-threaded contests, the .NET Framework supports the ThreadStatic attribute. You can apply this attribute only to static class fields – either fields defined in modules or fields defined in classes and marked with the Shared kewword:

    <ThreadStatic()> _
    Public UserName As String


All fields marked with the ThreadStatic attribute are stored in the Thread Local Storage (TLS) area. The separation among threads is automatic, because each thread owns a different TLS.

In theory, when migrating a VB6 DLL meant to be used by free-threaded clients, you should scrutinize each and every variable in BAS modules and decide whether to tag them with the ThreadStatic attribute. To make this task as easy as possible, as well as to avoid any manual edit of the resulting VB.NET code, starting with version 1.10.03 VB Migration Partner supports the ThreadSafe pragma, which can be applied at the project-, file-, method- and variable-level scope. In most cases, therefore, all you need to do is using a single pragma that affects all the variables in the current project:

    '## project:ThreadSafe True
    Public UserName As String



A common VB6 misconception... and how to cure it

clock July 31, 2008 12:17

One of our users is facing the migration of a large VB6 application and found that the coders often declared local variables using this "concise" syntax:

    Dim x1, x2, y1, y2 As Integer

It is evident that the original intention was to declare four Integer variables. The truth is, this statement declares only y2 as an integer variable, whereas the type of x1, x2, and y1 variables is affected by Defxxx statements (e.g. DefInt or DefDbl). If the current file contains no Defxxx statement, then the type of these variable is Variant. This was a common misconception among less experienced VB6 developers or developers that grew up with C or C++.

Both the Upgrade Wizard and VB Migration Partner convert the first three Variant variables into VB.NET "As Object" variables, which is formally correct but isn't what the original developer meant. Our user asked whether there was anything he could do about it. Quite surprisingly, VB Migration Partner offers a neat and elengant solution to this issue.

In fact, if you are migrating a VB6 application written by developers who consistently applied this sloppy coding practice, you can help VB Migration Partner to restore the intended data type by means of one or more PreProcess pragmas. More precisely, you need one pragma to account for Dim statements with two variables, another pragma to account for Dim statements with three variables, and so forth:

'## PreProcess "\b(?<kw>Private|Public|Dim|Static)\b\s+(?<v1>\w+)\s*,
\s*(?<v2>\w+)\s+As\s+(?<type>\w+(\.\w+)?)",
"${kw} ${v1} As ${type}, ${v2} As ${type}", true

'## PreProcess "\b(?<kw>Private|Public|Dim|Static)\b\s+(?<v1>\w+)\s*,
\s*(?<v2>\w+)\s*,\s*(?<v3>\w+)\s+As\s+(?<type>\w+(\.\w+)?)",
"${kw} ${v1} As ${type}, ${v2} As ${type}, ${v3} As ${type}", true

'## PreProcess "\b(?<kw>Private|Public|Dim|Static)\b\s+(?<v1>\w+)\s*,
\s*(?<v2>\w+)\s*,\s*(?<v3>\w+)\s*,
\s*(?<v4>\w+)\s+As\s+(?<type>\w+(\.\w+)?)",
"${kw} ${v1} As ${type}, ${v2} As ${type}, ${v3} As ${type}, ${v4} As ${type}", true

'## PreProcess "\b(?<kw>Private|Public|Dim|Static)\b\s+(?<v1>\w+)\s*,
\s*(?<v2>\w+)\s*,\s*(?<v3>\w+)\s*,\s*(?<v4>\w+)\s*,
\s*(?<v5>\w+)\s+As\s+(?<type>\w+(\.\w+)?)",
"${kw} ${v1} As ${type}, ${v2} As ${type}, ${v3} As ${type},
 ${v4} As ${type}, ${v5} As ${type}", true

Notice that these pragmas account for variables and class fields declared with Dim, Static, Public, and Private keywords. You can easily create similar PreProcess to account for more than five variables in a single line.

When these pragmas are used, the above Dim statement is expanded into the following VB6 code immediately before the migration begins:
    Dim x1 As Integer, x2 As Integer, y1 As Integer, y2 As Integer
which, of course, is translated to VB.NET as:
    Dim x1 As Short, x2 As Short, y1 As Short, y2 As Short

Important Note: Keep in mind that these pragmas affect may mistakenly affect the type of variables that SHOULD remain Object variables. Always double check that the resulting VB.NET code works as intended.

Problem solved! 

Update (October 3, 2008): Marco Giampetruzzi (from the VB Migration Partner Team) found a simpler and better way to solve the same problem, as you can read here.



Sharp enough to convert from VB6 to C#

clock July 28, 2008 01:36

Every now and then I visit the competition's web site (as much they visit ours) and recently stumbled into this post, which discusses whether VB6ers should migrate to C# rather than VB.NET. The sentence that hit my attention was

By the way, we’ve seen a few people suggesting a double path approach for those who chose to migrate their VB6 applications to C#. This idea mostly comes from those who offer a solution that only converts to VB.NET, and they even say it’s irrational to think about jumping from VB6 to any .NET language other than VB.NET. Well, the immense set of differences between VB6 and C# or VB.NET are accurately resolved by <name of their conversion tool>.

I had a friendly discussion with a couple of the author’s colleagues some time ago on this topic, therefore I assume that the “unnamed competitor” is Code Architects and our VB Migration Partner tool.

The first part of the sentence is absolutely correct: VB Migration Partner tool doesn’t generate C# code and we don’t plan to add this feature in the foreseeable future.

The reason is simple: our tool can generate VB.NET programs that are 100% equivalent to the original VB6 code, either at the first attempt or after adding a few migration pragmas. It converts Gosubs, On Goto/Gosubs, As Any parameters, As New variables and arrays with true auto-instancing semantics, default member references even in late-bound mode, deterministic finalization for IDisposable objects, arrays with any LBound, all 60+ controls in VB6 toolbox, ADO/RDO/ADO databinding, graphic methods, user-defined system coordinates, OLE drag-and-drop events, and many other features that the Upgrade Wizard doesn’t support.

Generating C# code would be a step in the wrong direction for us, because we couldn’t support several of the above features and we would therefore deliver lower quality code. We aren’t interested in that.

UPDATED (June 3, 2011):  never say never!... two years after this post was written, Visual C# 2010 introduced many interesting new features that make the migration from VB6 less error-prone. The first one is a (limited) form of late-binding (sort of), based on dynamic variables; the second one is the support for optional parameters. As a matter of fact, we have changed our mind and are working at a very innovative VB6-to-C# converter that works around the residual C#-related migration issues and still generate maintainable and efficient code. Stay tuned!

The second part of the sentence isn't correct, though. We don't say that it is irrational to convert a VB6 application to C# rather than VB.NET. We only claim that doing it in one single step using an automated conversion tool isn't a great idea for many reasons. Explaining these reasons requires a lengthy and detailed article, thus I thought I’d better post it here than just replying to the original post.

Comparing languages is a fascinating topic and I'd like to thank the author of the original post for giving me the excuse to explore it in depth. Please notice that I am not making any assumption on our competitor's VB6-to-C# converter: I never used it and they don't publish any converted VB.NET or C# application, as we do in our Code Samples section. Therefore this article is just an overview of some obvious issues that you have when converting VB6 code directly to C#.

An important disclaimer:  Let me emphasize that I don’t mean to compare the power of VB6/VB.NET vs. C#. The only purpose of this article is to demonstrate that there are VB6 peculiarities that can’t be easily migrated to C# code without loosing either readability or functional equivalence, especially if you use an automated code converter. In the best case you end up bloated and unreadable C# code, in the worst case you get C# code that doesn’t behave exactly like the VB6/VB.NET code you start with.


COMPARING VB6, VB.NET AND C#

VB.NET and C# languages aren't perfectly equivalent, therefore if you convert directly from VB6 to C# you nearly always lose something. (I wrote seven books on VB6, VB.NET and C# for Microsoft Press, thus I know what I am saying...) The best examples of this non-equivalence are the On Error statements and late binding. For example, consider this simple VB6 method that copies 10 files using the FileSystemObject type library:

Sub CopyFiles(ByVal throwIfError As Boolean)
    If Not throwIfError Then On Error Resume Next
    Dim fso As New FileSystemObject
    fso.CopyFile "sourcefile1", "destfile1"
    fso.CopyFile "sourcefile2", "destfile2"
    fso.CopyFile "sourcefile3", "destfile3"
    ' seven more CopyFile method calls …
End Sub

VB.NET fully supports the On Error Resume Next statement, therefore this code translates to VB.NET as-is, except for the parenthesis around method arguments. Let’s see now the “equivalent” C# implementation:

void CopyFiles(bool throwIfError)
{
    Scripting.FileSystemObject fso = new Scripting.FileSystemObjectClass();
    try
    {
        fso.CopyFile("sourcefile1", "destfile1", true);
    }
    catch
    {
        if (throwIfError)
        {
            throw;
        }
    }
    try
    {
        fso.CopyFile("sourcefile1", "destfile1", true);
    }
    catch
    {
        if (throwIfError)
        {
            throw;
        }
    }
    try
    {
        fso.CopyFile("sourcefile1", "destfile1", true);
    }
    catch
    {
        if (throwIfError)
        {
            throw;
        }
    }
    // seven more try-catch blocks
}

We started with a VB6 method containing 12 executable statements and ended up with a C# method of 111 statements, nearly 10x bigger! You can’t always have concise C# code if your main goal is functional equivalence with the original VB6 code, sorry!

Also, notice that the C# code can’t omit the third (optional) argument in the FileSystemObject.CopyFile method, which makes the code even more verbose. Just imagine what happens when working with methods that have many optional parameters, such as those exposed by Microsoft Office type libraries! (Note that I purposely avoided to convert the FileCopy method into System.IO.File.Copy, because the focus here is on error handling, not file operations.) 

Don’t miss another important point: I translated the VB6 code to C# manually. An automatic translator tool might fail to deliver code that is truly equivalent and might, for example, bracket all the statements in a single try-catch block and add a warning about the different semantics and behavior. The text of such a warning may vary, but it would be a variation of the following sentence:  “My job ends here, it’s up to you ensuring that I didn’t mess things up.” Laughing

You might believe that the problem surfaces only with the On Error Resume Next statement, and that converting the On Error Goto <label> command can be easily translated to equivalent C# code. Alas, this isn't the case if you have multiple On Error Goto statements in one method, or if you use the Resume Next statement, or if you turn error handling on and off by alternating On Error Goto <labal> and On Error Goto 0 commands. Code of this sort can be a nightmare even for the bravest developer, and I doubt a VB6-to-C# convert can handle it correctly. Of course, you don't have any problem if you convert to VB.NET, because all these variants are fully supported by VB.NET.

By the way, if you are curious about how often you’ve used On Error statements it’s time to run our free VB6 Bulk Analyzer tool. You’d be surprised!

Let’s now tackle late binding. You can use late-binding in both VB6 (always) and in VB.NET (if Option Strict is Off), therefore translating the following VB6 code to VB.NET is a trivial task for both humans and automatic translators:

Sub IncrementValue(ByVal obj As Object, ByVal valueBeingAdded As Object)
    obj.Value = obj.Value + addedValue
    obj.Refresh
End Sub

C# doesn’t support late binding, thus there is no easy way to render this code into C# short of using reflection. This is how I’d translate it, after adding a variable and a few remarks to make it more readable:

void IncrementValue(object obj, object valueBeingAdded)
{
    // read Value property
    object value = obj.GetType().InvokeMember("Value",
       
System.Reflection.BindingFlags.GetProperty, null, obj, null);
    // increment it (assumes it's a double)
    object newValue = Convert.ToDouble(value) + Convert.ToDouble(valueBeingAdded);
    // assign back to Value property
    obj.GetType().InvokeMember("Value",
        System.Reflection.BindingFlags.SetProperty, null, obj,
        new object[] { newValue });
    // call the Refresh method
    obj.GetType().InvokeMember("Refresh",
        System.Reflection.BindingFlags.InvokeMethod, null, obj, null);
}

At least, this C# code is perfectly equivalent to the original VB6 code, right?

Wrong. In fact, the VB6 "+" operator also works with strings, therefore I can’t blindly assume that the Value property is numeric. As a matter of fact, when working with late binding you can’t assume anything about the objects you work with. Here’s the new version that works with strings and numeric properties:

void IncrementValue(object obj, object valueBeingAdded)
{
    // read Value property
    object value = obj.GetType().InvokeMember("Value",
        System.Reflection.BindingFlags.GetProperty, null, obj, null);
    // either increment it (assumes it's a double) or append the string
    object newValue;
    if (value is string)
    {
        newValue = Convert.ToString(value) +
            Convert.ToString(valueBeingAdded);
    }
    else
    {
        newValue = Convert.ToDouble(value) +
            Convert.ToDouble(valueBeingAdded);
    }
    // assign back to Value property
    obj.GetType().InvokeMember("Value",
        System.Reflection.BindingFlags.SetProperty, null, obj,
        new object[] { newValue });
    // call the Refresh method
    obj.GetType().InvokeMember("Refresh",
        System.Reflection.BindingFlags.InvokeMethod, null, obj, null);
}

Do we now have a piece of C# that is perfectly equivalent to the original VB6 code? The answer is again no, because there are many other details that I should account for, including:

a) I assumed that Value is a property – the C# code fails if Value is a class field, unlike VB6 and VB.NET
b) I assumed that Refresh has no parameters – the VB6 and VB.NET code works correctly even if Refresh has one or more optional arguments, but the C# code fails in this case
c) I didn’t care about error codes: if an overflow occurs or if the object doesn’t expose the Value or Refresh members, I should ensure that the caller code sees the same error code that it would see under VB6 or VB.NET.

In the (very unlikely) hypothesis that you never, ever used On Error Resume Next statements and late binding calls, let’s see an example that uses two nested loops and leverages the fact that the Exit statement allows you to specify whether you want to leave the Do loop or the For loop. (I could have made the example more complex by adding a While…Wend loop.)

Sub FindElement(arr() As Integer, ByVal value As Integer)
    Dim row As Integer, col As Integer
    ' outer loop
    For row = 0 To UBound(arr)
        ' inner loop
        col = 0
        Do While col <= UBound(arr, 2)
            If arr(row, col) >= value Then Exit For
            col = col + 1
        Loop
    Next
    Debug.Print "FOUND AT ROW=" & row & ", COL=" & col
    ' additional code here...
End Sub

This code converts to VB.NET without any problem, but requires some extra work to convert to C#:

void FindElement(int[,] arr, int value)
{
    int row = 0;
    int col = 0;
    // outer loop
    row = 0;
    while (row < arr.GetUpperBound(0))
    {
        // inner loop
        col = 0;
        while (col < arr.GetUpperBound(1))
        {
            if (arr[row, col] >= value)
            {
                goto ExitOuterLoop;
            }
            col++;
        }
        row++;
    }
ExitOuterLoop:
    System.Diagnostics.Debug.WriteLine("FOUND AT ROW=" + row.ToString()
        + ", COL=" + col.ToString()); 
    // additional code here...
}

Aarghh! The dreaded GoTo! I like concise and readable code, but I surely don’t want my code to be infested by Goto statements, therefore I rewrote the code the way most C# developers would do:

void FindElement(int[,] arr, int value)
{
    int row = 0;
    int col = 0;
    bool found = false;
    // outer loop
    row = 0;
    while (row < arr.GetUpperBound(0))
    {
        // inner loop
        col = 0;
        while (col < arr.GetUpperBound(1))
        {
            if (arr[row, col] >= value)
            {
                found = true;
                break;
            }
            col++;
        }
        If ( found )
        {
            break;
        }
        row++;
    }
    System.Diagnostics.Debug.WriteLine("FOUND AT ROW=" + row.ToString()
        + ", COL=" + col.ToString()); 
    // additional code here...
}

Not counting remarks, we started from a 11-line VB6 method and ended up with a C# method containing 26 lines, a 236% “improvement.” 

To recap: I picked three simple VB6 examples that use common features – error handling, late-binding, and nested loops- and in all cases I ended up with more verbose and less readable C# code, in spite of all my attempts to make the C# code look like “native” C#. Never forget that the situation can only get worse if the translation is performed by means of a software tool.

The bottom line: converting from VB6 to “equivalent” C# often delivers ugly, verbose, unmaintainable code or code that isn’t perfectly equivalent to the original VB6 code.

In some cases you can come up with C# code that approximates the original VB6 code’s behavior, but that’s a completely different story. When migrating an application with 1 million code lines, I don’t want to worry about the thousands of occurrences of On Error statements or late-bound calls. Not to mention other important differences such as arrays with any LBound, auto-instancing (As New) variables and arrays thereof, fixed-length strings, and so forth.


USING VB6-TO-C# CONVERTERS (aka ONE DOUBLE-JUMP)

Once again, it is important to bear in mind that in all previous examples I made a manual translation from VB6 to C# and came up with the most efficient and less verbose C# that is equivalent to the original VB6 code. Sure, you can drop a few curly braces and merge a couple of lines into a single statement, but I would be surprised if it were possible to significantly improve the C# code seen above.

Actually, I would be very surprised if a VB6 conversion tool could be able to generate the kind of C# code that a human developer would write. My guess is that in the best cases you’d see many calls to support methods – e.g. to work around late-binding deficiencies or simulate methods in the VB library – and a lot of code that just doesn’t “look like” C#.

In the worst (and more frequent) cases, you’d obtain C# code that isn’t perfectly equivalent to the original VB6 code and that forces you to carefully test each and every method that uses error handling, late binding, auto-instancing (As New) variables, arrays, file operations... in other words, virtual every single statement in your application.

If you are switching to C# with the purpose of having well-structured and readable code that performs at least as well as the original VB6 code, you are not going to achieve this goal by means of an automatic VB6-to-C# converter.

USING VB.NET-TO-C# CONVERTERS (aka TWO SINGLE-JUMPS)

At times you really need to convert from VB6 to C#. My recommendation is that you should try hard to convince your (internal or external) customer that translating to C# gives you more headaches than benefits. …but for the sake of discussion, let’s say that you have no choice and that C# is the target language.

Even in such case, a double-jump from VB6 to C# using an automatic converter isn’t the best option, both for technical and business reasons. It is much more rational jumping from VB6 to VB.NET and then using a VB.NET-to-C# converter to get the final result.

I built my opinion on many facts:

a) translating to VB.NET is simpler and faster – either using the free Upgrade Wizard that comes with Visual Studio or a commercial tool such as our VB Migration Partner. You can have a prototype sooner and you can bring a first version to the market in a fraction of the time it takes to just have a C# project that compiles without errors.

b) if you have a group of VB6 developers, odds are that they are already familiar with VB.NET. If they aren’t, they can learn VB.NET quickly because the two languages are similar. By comparison, becoming proficient with C# is a longer and more expensive process. By converting to VB.NET first you leverage the experience of your developers and reduce overall migration costs. (Don’t forget that C# developers usually ask for more money.)

c) There are several VB.NET-to-C# converters on the market, including the exceptional Instant C# (by Tangigle Software Solutions) and C-Sharpener for VB by Elegance Technologies. They deliver the best C# code that a software can automatically do. The companies behind these tools have just one mission: translating from VB.NET to C# in the best way, and in fact they are often updated and produce code that is more and more optimized and each new release. I use one of them and I am entirely satisfied.

d) from the business perspective, it is more convenient to purchase two separate tools, a VB6-to-VB.NET converter and a VB.NET-to-C# converter, because you can use the latter in other circumstances. If you have a tool that converts from VB6 to C# in one step, you can't use it to convert the VB.NET code that you happen to have already (for example ASP.NET pages) or that had to be translated manually from VB6 because of some deficiency in the migration tool.

e) speaking of convenience, the Standard Edition of both Instant-C# and C-Sharpener for VB cost less than 200$, which is a real bargain considering how useful they can be in many situations. This price is negligible if compared with the total cost of the migration of a real-world VB6 business application.

One might object that using a single integrated tool is better than using two distinct converters. But consider this: our VB Migration Partner converts at about 18,000 lines per minute; one of these converters splits out code at about 15,000 lines per minute; both of them come with a batch version, therefore you can easily automate your VB6-to-C# conversions. By combining them you can migrate a 100,000 line VB6 project to C# in about 12 minutes, 2x or 3x faster than what it takes to the Upgrade Wizard to perform just the first jump to VB.NET.



Refactoring VB.NET code with regular expressions

clock July 21, 2008 10:22

One of the key features of VB Migration Partner is migration pragmas, and possibly the most powerful pragma is PostProcess, which enables you to modify the VB.NET being generated. The PostProcess pragma is based on regular expressions and works much like the Regex.Replace method.

The PostProcess pragma can really do wonders in the hands of a developer who is familiar with regexes. As a matter of fact, we have already uploaded several KB articles that show how you can refactor your code using PostProcess pragmas:

[HOWTO] Modify project-level options in VB.NET programs
[HOWTO] Move the declaration of a variable into a For or For Each loop
[HOWTO] Transform “Not x Is y” expressions into “x IsNot y” expressions
[HOWTO] Replace App6 properties with native VB.NET members
[HOWTO] Get rid of warnings related to Screen.MousePointer property
[HOWTO] Convert While loops into Do loops
[HOWTO] Simplify code produced for UserControl classes
[HOWTO] Change the base class of a VB.NET class

[HOWTO] Avoid compilation errors caused by unsupported designers

In this post I am going to show you how you can use the PostProcess pragma in a creative way to optimize common Boolean expressions. The regex patterns I will show aren't exactly simple and I won't explain each in depth. But trust me, they work and they work great!

Important note #1: like all substitutions based on regular expressions, there is a small probability that the techniques discussed in this article might suffer from false matches and, therefore, might produce invalid VB.NET code. Ensure that you add these pragmas to your VB6 code only after you have checked that the conversion works well and be prepared to remove these pragmas if you notice that they produce invalid VB.NET code.

Important note #2: most regex patterns in this article are too long for the page width and will be split in two or more lines. If you are in trouble, at the bottom of this KB article you will find a link to a text file that gathers all of the PostProcess pragmas I am introducing here.

1. Comparisons with True and False

Some VB6 developers like to explicitly compare a Boolean variable with True or False, as in this example:
    Dim x As Integer, res As Boolean, res2 As Boolean
    ' ...
    If res = True And res2 = False Then x = 0


Explicit comparison of a Boolean variable with True or False adds a little overhead, which can be avoided by re-rewriting the expression as follows:
    If res And Not res2 Then x = 0

Here are the two PostProcess pragmas that do the trick, the former accounting for comparisons with True and the latter for comparisons with False:

'## PostProcess "(?<=\n[ \t]*)(?<key>If|ElseIf)\b(?<pre>.+)\b(?<cond>\S+)
    [ \t]*=[ \t]*\bTrue\b(?<post>.+)\bThen\b",
    "${key}${pre}${cond}${post}Then", True

'## PostProcess "(?<=\n[ \t]*)(?<key>If|ElseIf)\b(?<pre>.+)\b(?<cond>\S+)
    [ \t]*=[ \t]*\bFalse\b(?<post>.+)\bThen\b",
    "${key}${pre}Not ${cond}${post}Then", True


The condition can appear also inside Do, Loop, and While statements, thus we need two more PostProcess pragmas:

'## PostProcess "(?<=\n[ \t]*)(?<key>(Do|Loop)[ \t]+(While|Until)|While)\b
    (?<pre>.+)\b(?<cond>\S+)\s*=\s*\bTrue\b",
    "${key}${pre}${cond}", True

'## PostProcess "(?<=\n[ \t]*)(?<key>(Do|Loop)[ \t]+(While|Until)|While)\b
    (?<pre>.+)\b(?<cond>\S+)\s*=\s*\bFalse\b",
    "${key}${pre}Not ${cond}", True


2. Not keyword in Do While and Loop While statements

If a Do While or Loop While statement is followed by a condition that is prefixed by the Not keyword, you can drop the Not keyword and transform the statement into Do Until or Loop Until, respectively. For example, the following code
    Do While Not res
        ' ...
    Loop

can be transformed into:
    Do Until res
        ' ...
    Loop


This is the PostProcess pragma that can perform this substitution for you:

'## PostProcess "(?<=\n[ \t]*)(?!.*\b(And|Or|Xor)\b)(?<key>Do|Loop)
    [ \t]+While[ \t]+Not[ \t]+(?<cond>.+)", "${key} Until ${cond}", True


Notice that the regex pattern fails if the condition contains an And, Or, or Xor operator. This check is necessary to avoid to mistakenly match an expression such as this:
    Do While Not res And x = 10

3. Not keyword in Do Until and Loop Until statements

If a Do Until or Loop Until statement is followed by a condition that is prefixed by the Not keyword, you can drop the Not keyword and transform the statement into Do While or Loop While, respectively. For example, the following code
    Do Until Not res
        ' ...
    Loop

can be transformed into:
    Do While res
        ' ...
    Loop


The PostProcess pragma that can perform this substitution is as follows:

'## PostProcess "(?<=\n[ \t]*)(?!.*\b(And|Or|Xor)\b)(?<key>Do|Loop)
    [ \t]+Until[ \t]+Not[ \t]+(?<cond>.+)", "${key} While ${cond}", True


4. Assigning True/False to a variable in a If...Then...Else block

A common (yet inefficient) coding pattern among developers is to use an If…Then…Else block to assign True or False to a Boolean variable. For example, consider the following VB6 code:
    Dim x As Integer, res As Boolean, res2 As Boolean
    ' ...
    If x <> 0 Then
        res = True
    Else
        res = False
    End If            

    If x > 10 And x < 20 Then
        res2 = False
    Else
        res2 = True
    End If


It is evident that it can be simplified (and optimized) as follows:

    res = (x <> 0)
    res2 = Not (x > 10 And x < 20)


Creating a regular expression pattern that allows you capture and transform the If…Then…Else block isn’t a trivial task, also because we need a different PostProcess pragma to account for single-line If statements:

'## PostProcess "(?<=\n[ \t]*)If[ \t]+(?<cond>.+?)[ \t]+Then[ \t]*\r?\n[ \t]
    *\b(?<var>(Return\b|\S+[ \t]*=))[ \t]*True[ \t]*\r?\n[ \t]*Else
    [ \t]*\r?\n[ \t]*\k<var>[ \t]*False[ \t]*\r?\n[ \t]*End[ \t]+If
    [ \t]*\r?\n", "${var} (${cond})\r\n", True

'## PostProcess "(?<=\n[ \t]*)If[ \t]+(?<cond>.+?)[ \t]+Then[ \t]+
    (?<var>(Return\b|\S+[ \t]*=))[ \t]*True[ \t]+Else[ \t]+\k<var>
    [ \t]*False[ \t]*\r?\n", "${var} (${cond})\r\n", True


The next two PostProcess pragmas account for the cases when the variable is assigned False when the condition is true. Again, we need a second pragma to account for single-line IF statements.

'## PostProcess "(?<=\n[ \t]*)If[ \t]+(?<cond>.+?)[ \t]+Then[ \t]*\r?\n[ \t]
    *\b(?<var>(Return\b|\S+[ \t]*=))[ \t]*False[ \t]*\r?\n[ \t]*Else
    [ \t]*\r?\n[ \t]*\k<var>[ \t]*True[ \t]*\r?\n[ \t]*End[ \t]+If
    [ \t]*\r?\n", "${var} Not (${cond})\r\n", True

'## PostProcess "(?<=\n[ \t]*)If[ \t]+(?<cond>.+?)[ \t]+Then[ \t]+
    (?<var>(Return\b|\S+[ \t]*=))[ \t]*False[ \t]+Else[ \t]+\k<var>
    [ \t]*True[ \t]*\r?\n", "${var} Not (${cond})\r\n", True


Notice that the patterns that follow account both for variable assignements and for the Return keyword used to return a value from a function (such Return keyword is used by VB Migration Partner if possible).

5. Assigning different values to a variable inside an If...Then...Else block

Another common coding pattern - which is actually a variant of the previous one - is to use an If…Then…Else block to assign one of two possible values to the same variable, as in these examples:
    If x <> 0 Then
        y = x + 10
    Else
        y = 20
    End If

    ' single-line variant
    If x <> 0 Then y = x + 10 Else y = 20


There is a more concise – though not necessarily more efficient – way to perform the same assignment:
    y = IIf(x <> 0, x + 10, 20)

If you are interested in performing this substitution automatically, you can use the following two pragmas, where the latter works for single-line IF statements:

'## PostProcess "(?<!\bEnd[ \t]+)\bIf[ \t]+(?<cond>.+?)[ \t]+Then[ \t]*\r?\n
    [ \t]*\b(?<var>(Return\b|\S+[ \t]*=))[ \t]*(?<v1>[^\r\n]+)\r?\n
    [ \t]*Else[ \t]*\r?\n[ \t]*\k<var>[ \t]*(?<v2>[^\r\n]+)[ \t]*\r?\n
    [ \t]*End[ \t]+If[ \t]*\r?\n",
    "${var} IIf(${cond}, ${v1}, ${v2})\r\n", True

'## PostProcess "(?<!\bEnd[ \t]+)\bIf[ \t]+(?<cond>.+?)[ \t]+Then[ \t]+
    (?<var>(Return\b|\S+[ \t]*=))[ \t]*(?<v1>[^\r\n]+)\bElse[ \t]+\k<var>
    [ \t]*(?<v2>[^\r\n]+)\r?\n",
    "${var} IIf(${cond}, ${v1}, ${v2})\r\n", True


---------------------------

That's it, folk! If you ever wondered why you should care about regular expressions, I hope I gave you something to think about Wink



Bugs that drove us crazy

clock June 25, 2008 06:02

We all know that writing software requires a vast assortment of skills and, above all, a lot of patience. You usually need the latter to feel comfortable when you realize that you've wasted hours and days over someone else's bug.  Here's the story.

Like many commercial applications, VB Migration Partner has a splash screen. No big deal. VB Migration Partner is written in VB2005, therefore we could use VB.NET application framework. As a matter of fact, defining a splash screen in VB.NET is as easy as selecting an item in a combobox in the Application tab of the My Project designer.



During the beta test period a few users reported that at times VB Migration Partner threw an exception at launch, immediately after closing the splash screen. We never managed to reproduce the problem on our computers. Fortunately the exception occured quite rarely. Until a user running Windows 2000 reported that he was seeing the exception at nearly every launch. After installing VB Migration Partner on Windows 2000 in our lab, we could indeed verify that we had a problem.

 

It took a while to find out that we weren't  the only ones suffering from this issue. As a matter of fact, Microsoft recognizes that it is a VB.NET bug. Fortunately, they also offer a workaround. According to whis workaround you must create an invisible form from inside the Form_Load event of your splash screen, something like this:

   Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) _
          Handles MyBase.Load
     Dim frm As New Form
      frm.Opacity = 0
      frm.ShowInTaskbar = False
      frm.FormBorderStyle = FormBorderStyle.None
      frm.Show()
      Return frm
   End Function

Unbelievably, this code makes the problem vanish away! You can find more details in this Microsoft KB article.

We realized that *all* applications written in VB.NET could suffer from this issue, thus we added a method to our support library, named SplashScreenBugFix6, which creates the invisible form for you. You just need to add an InsertStatement pragma in the original VB6 splash screen form, so that a call to this method is automatically generated anytime you convert the VB6 project:

    ' in the VB6 project
    Private Sub Form_Load()
      '## InsertStatement SplashScreenBugFix6()
      ' more code in the Load event
   End Sub



Neat tricks for smooth migration of calls to Windows API methods

clock June 13, 2008 03:13

VB Migration Partner does a superb in dealing with Windows API calls. Here's a summary of the features that it supports and that are out of reach for the Upgrade Wizard included in Visual Studio 2005/2008:

  • converts As Any parameters, by creating all the necessary overloads
  • deals correctly with API methods that take a callback address (e.g. EnumWindows, EnumFonts)
  • provides recommendation about the .NET object/method that can effectively replace the API method; we cover 300+ different API calls
  • in a few cases, it automatically replace API calls with calls to the corresponding .NET object/method
  • ensures that string immutability doesn't prevent the VB.NET code from working correctly (see this article)
  • generates the correct MarshalAs attributes for elements in Type (Structure) blocks
  • correctly translates fixed-length strings inside Type blocks, so that they work correctly when passed to the Windows API method
  • automatically initializes static arrays inside Type blocks, so that you don't get unexpected crashes when invoking an API method that expects to find a buffer there
  • creates a wrapper method that ensures that orphaned delegates don't cause an unexpected runtime exception, an advanced programming technique discussed in this KB article
  • includes the VB6WindowsSubclasser class that helps you correctly migrate subclassing-based techniques (as explained here)


In spite of all these innovations, there are cases when you still need to manually edit either the original VB6 code or the converted VB.NET. This happens, for example, if the original code uses the VarPtr, StrPtr, or ObjPtr functions to pass memory pointers to an external API method. These three functions aren't supported under VB.NET and there is no simple way to simulate them.

The good news is that in the vast majority of cases you don't need to deal with memory pointers under .NET, because the .NET Framework offer a valid "pure" alternative to the API method in question. In this article I'll illustrate how you can take advantage of VB Migration Partner features to reduce manual edits to the very minimum and take advantage of the convert-test-fix methodology.

For simplicity's sake, let's focus on one of the simplest API methods, the GetSystemDirectory Windows API. Here's a piece of VB6 code that displays the system directory path:

    ' Main.Bas module
    Public Declare Function GetSystemDirectory Lib "kernel32.dll" Alias "GetSystemDirectoryA" _
        (ByVal lpBuffer As String, ByVal nSize As Long) As Long

    Sub Main()
        Dim buffer As String, length As Long, windir As String
        buffer = Space(256)
        length = GetSystemDirectory(buffer, Len(buffer))
        winDir = Left(buffer, length)
        MsgBox winDir
    End Sub

The first step is in refactoring this code so that you make all the Declares private and move them to another BAS module, that exposes them by means of standard VB6 methods. (If you usually write tidy and maintainable VB6 code, odds are that you have already taken this step.)

    ' This is the APIHelpers.Bas file
    Private Declare Function GetSystemDirectory Lib "kernel32.dll" Alias "GetSystemDirectoryA" _
        (ByVal lpBuffer As String, ByVal nSize As Long) As Long

    ' returns the Windows directory
    Public Function SystemDirectory() As String
        Dim buffer As String, length As Long
        buffer = Space(256)
        length = GetSystemDirectory(buffer, Len(buffer))
        SystemDirectory = Left(buffer, length)
    End Function

The code that actually displays or otherwise uses the Windows directory path is now simpler. Notice that we explicitly include the module name (APIHelpers) in the method call. This tip reduces the odds that another method with same name exists elsewhere in the project, but the technique explained later works even if you don't include such a name:

    ' the Main.Bas module
    Sub Main()
        Dim windir As String
        winDir = APIHelpers.SystemDirectory
        MsgBox winDir
    End Sub

At this point you have a VB6 project that works exactly like the original one, but it is better organized and structured, with all Declares statements gathered in one single module. Let's see how to migrate this code to VB.NET and get rid of all dependencies from non-NET code.

First and foremost, we prepare a VB.NET module that exposes the same methods as the original APIHelpers.bas but doesn't use any Declare statement. Here's how we can render the SystemDirectory function using native .NET calls:

    ' This is the APIHelpers.vb file (VB.NET)
    Module APIHelpers
        Public Function SystemDirectory() As String
            Return Environment.SystemDirectory
        End Function
    End Module

Next, we use an ExcludeCurrentFile pragma to exclude the APIHelpers.bas VB6 module from migration process and we use an AddSourceFile pragma to add the APIHelpers.vb VB.NET file to the converted Visual Studio project. The neat result is that the code in Main now calls the .NET version of the method, which doesn't use any unmanaged calls:

    ' This is the APIHelpers.Bas file
    '## ExcludeCurrentFile
    '## AddSourceFile "c:\vbnet\modules\apihelpers.vb"

    Private Declare Function GetSystemDirectory Lib "kernel32.dll" Alias "GetSystemDirectoryA" _
        (ByVal lpBuffer As String, ByVal nSize As Long) As Long
    ' ... remainder of module as before...

This solution works great, but we can improve it. In fact, a (minor) problem is that the resulting VB.NET code still uses wrapper methods and doesn't look like the "native" .NET code that an experienced VB.NET developer would write. Fear not, because all you need is a project-level PostProcess pragma:

    ' This is the APIHelpers.Bas file
    '## ExcludeCurrentFile
    '## project:PostProcess "(APIHelpers\.)?SystemDirectory", "Environment.SystemDirectory"

Notice that I dropped the AddSourceFile pragma because you don't need the wrapper method any longer (at least in this simplified example). Using similar techniques you can provide a .NET equivalent for most methods that require API calls under VB6, including methods that take arguments.

One of the long-terms goals we have in Code Architects is to apply these concepts on a larger scale to create VB6 helper modules and their corresponding VB.NET versions, to help all VB6 developers to easily migrate their API-intensive applications. Just stay tuned, as usual!



What are your options to survive in the next decade?

clock May 29, 2008 07:51

Well, I really mean "what are the options to have your VB6 application survive in the next decade?". After nearly 30 years in the field - I started my Computer Science studies in 1979, go figure! - many of which spent consulting for companies in Europe and United States, I have seen many tecnology revolutions, including the advent of IBM PC and of Windows 1.0. Each one thaught me a little piece of experience.

Let's quickly see the alternatives. 

1) DO NOTHING (AKA "IF IT AIN'T BROKE, DON'T FIX IT")
If the application is at the end of its life cycle, for example because it doesn't fullfil a widespread need any longer, you might decide to have it die of a natural death. There is no reason to invest in a dying creature, therefore you might stay with VB6 and just fix some major bugs when your users complain aloud. Don't add any new feature or extend it in any way, because it would be too expensive too.

2) EXTEND THE VB6 APPLICATION USING COM INTEROP AND THE FORMS INTEROP TOOLKIT
In this case you leave the main application in VB6 but extend it by invoking .NET components using COM Interop, or display .NET forms using Microsoft's Forms Interop Toolkit. Version 2.0 of this add-on is a major improvement, as it even supports hosting of .NET controls inside VB6 forms. Best of all, the toolkit is fully supported by Microsoft and comes with full source code. [Thanks to Rob Windsor for remainding me of this option, see comments]

While the toolkit is free, adopting it has some obvious and hidden defects and costs, though. First and foremost, this approach isn't really a solution to the problem, is more of a technique to soften the migration path and lessen its impact on your organization. If you need to migrate because your app doesn't work well under Windows Vista, or because it's slow, or because you don't want to rely on a language that Microsoft doesn't support any longer, using COM Interop and the Forms Interop Toolkit is just a way to postpone the time when you truly need to find a reliable solution. Second, the communication between the VB6 and VB.NET portions of the application is often awkward and leads you to inefficiencies (due to COM Interop) and complex roundtrips that don't make your code easily maintanable.

The bottom line: you should use the Forms Interop Toolkit only in the bigger picture of a phased migration (as Rob suggests in this comment). That is, while a group of developers is working on the porting from VB6, another group is already extending the application using VB.NET. Before taking this approach, you should compare it with the effort required by migrating it to VB.NET in one step using a *good* migration tool (ours, that is :-) )

3) CONVERT TO VB.NET, USING THE UPGRADE WIZARD AVAILABLE IN VISUAL STUDIO
Why not giving it a try? it's free! While your hard disk spins for hours trying to convert your real-world business app, you can have enough time to google for less-then-gentle comments about this tool from the VB community. Let's skip to next option.

4) CONVERT TO VB.NET, USING VB MIGRATION PARTNER
Using our conversion tool to migrate to VB.NET makes sense in many cases. First, when the original application works well and is thoroughly tested, but needs to be extended with new features and implementing these extensions with VB6 would be too difficult or expensive. Second, when the application is so large that rewriting it from scratch would require a very intensive test phase. Third, if time-to-market is essential to beat your competition and make your existing users happy. In these cases you can leverage VB Migration Partner higher speed and precision (as noted by our beta testers)

5) CONVERT TO VB.NET, USING ANOTHER 3RD-PARTY TOOL
There are other VB6-to-VB.NET migration tools on the market. Choosing a conversion tool is a critical decision, so you might want to ask our competitors for a trial version and compare it with our VB Migration Partner. Please do it! Please compare its feature, precision, and speed with VB Migration Partner and then let us know. If you don't have time to waste, however, ask yourself the following questions before considering another conversion tool:

a) how many VB Migration Partner's feature does this tool support? for example, does it fully support all 60+ VB6 controls, drag-and-drop, graphic methods, arrays with any LBound, Gosubs, As New semantics, IDisposable objects, As Any parameters and callbacks in Declares, etc. 
b) are there any significant features that this tool support and that VB Migration Partner doesn't?
c) why they don't make their entire documentation available online before you buy?
d) why don't they dare to publish real-world VB6 apps and the VB.NET code that their tool generates?

As far as we know, Code Architects is the only vendor who has uploaded dozens of VB6 code samples and the corresponding, converted VB.NET project, to let potential customers see and test the VB.NET applications we produce. We don't upload artificially-conceived tiny VB6 projects that highlights the tool's strengths and hide its weakness. Instead, our code sample section gathers open-source VB6 projects taken from the Internet, and we guarantee that we edited neither the original VB6 code (which is in fact downloadable from its original URL) nor the converted VB.NET.

Now ask yourself: why no other vendor took this obvious step to show the world how powerful their tool is?

6) MANUALLY RE-WRITE IT TO VB.NET
In some cases, manually re-writing the application to VB.NET is a viable solution. Well, let me be absolutely honest on this point: in (very) few cases, this is the solution we recommend to our customers. If the application is a huge amount of contorted spaghetti code, has a orrible architecture and a ugly user interface....well, in this case you don't want to spend more time and money on automatic porting, because you would also port these issues to VB.NET. 

When weighing a complete re-writing from scratch against automatic porting, you should take several factors into account, including:

a) rewriting often takes from 50% to 90% of the time and money that was necessary to write the original application. (Sorry, no official statistics on this, just our field experience...)
b) you need a team of developer who are experienced in both VB6 and VB.NET, and they also need to be familiar with issues related to the specific application, therefore you can't simply hire them from outside your company
c) how do you cope with continuous edits/improvements to the original VB6 code during the months required by the rewrite? In other words, can you count on something similar to our convert-test-fix methodology?
d) can really you afford the extra time required by a complete re-write? what will your competitor do in the meantime?

7) MANUALLY RE-WRITE TO C#, JAVA, OR ANOTHER LANGUAGE
I heard that a few ISVs are taking this path. In my opinion this is an irrational decision, often motivated by the anti-VB (or even anti-Microsoft) feeling that is so fashionable today. Let me explain why you shouldn't even think of jumping from VB6 to any .NET language other than VB.NET.

Rewriting a complex VB6 application into a different language has all the drawbacks of rewriting it to VB.NET (see point 6), plus the many problems that you have when switching from VB.NET to C# or Java. How do you implement On Error Resume Next in C#? How do you render a DTPicker control in Java without carefuly mapping each and every property, method, and event? But the decisive argument is: do you have enough developers who are equally familiar with VB6 and C# or Java and the application being migrated?

Briefly stated: taking this route is a technological suicide similar to point 1), except it takes longer, costs more, and is more agonizing. You can only hope that your competitors are in love enough with C# or Java to invest their energies in this option. If you hear that they are doing it, relax and be happy: you don't have anything to worry about for a long time :-)

8) CONVERT IT TO C# USING AN AUTOMATIC CONVERSION TOOL
Come on! To get an idea of how tough this "solution" (so to speak) is, take all the issues listed at point 5) and multiply them by all the problems mentioned at point 7). Then double the result to have a more reasonable estimate of the actual cost and effort.

More seriously, just think of this: there are a few commercial tools on the market that can automatically migrate VB.NET to C#, for example Instant C# or C-Sharpener for VB. I have actually used a couple of these tools, they do a great job. However, they still require a few manual edits after the conversion, to adjust minor differences between VB.NET and C#. The main reason is that the resulting C# code doesn't look like native C# and requires some fixes to be more readable and easily maintainable.

Before your consider this approach, ask yourself two questions:

a) if you believe that automatic conversion between two .NET languages (VB.NET and C#) is difficult, how can you expect that a tool be able to automatically convert from VB6 to C# in one single step?
b) on the other hand, if you think that conversion from VB.NET to C# can be fully automated, why not going from VB6 to VB.NET using our VB Migration Partner (or another tool, if you prefer) first and then using Instant-C, C-Sharpener for VB, or another similar tool for doing the final move to C#?

If you really want a C# app as the final deliverable of your conversion efforts, keep in mind that jumping from VB6 directly to C# doesn't give you more than switching from VB6 to VB.NET first and then from VB.NET to C#. Well, it actually does give you something more.... many more headaches, for precision's sake :-)



Comparing the & operator, the String.Concat method and the String.Format method

clock February 20, 2008 12:16

You might be aware that VB.NET supports at least 4 techniques to append strings:

    1) the & operator (or the + operator)

    2) the String.Concat static method

    3) the String.Format static method

    4) the StringBuilder object and its Append and Insert methods

By peeking at the code that the VB.NET compiler generates for the & (or +) operator, you can quickly realize that this operator is rendered as a call to the String.Concat method, thus using either technique #1 or #2 has no impact on performance and is just a matter of coding style. I usually prefer the & operator over String.Concat for readibility's sake, and I guess most VBers do the same, but it should be clear that the two are perfectly equivalent if you are concerned only about speed. (NOTE: when working with values other than strings, you might find String.Concat more readable, because it doesn't force you to explicitly invoke the ToString() method of each operand.)

When working with long strings that undergo many concatenations, the StringBuilder object is the fastest technique, period. Too much digital ink has been spilled on this topic, and I won't repeat those well-known concepts here. (BTW, if you're looking for a smart way to replace the & operator with the StringBuilder object without messing up your existing code, read here.)

However, when you have to perform a few concatenations on many short strings, the overhead needed to setup the StringBuilder object often shadows the benefits of its Append method. In these scenarios, the choice is among techniques #1 (or #2, which is equivalent) and technique #3. Today I decided to take some time to compare their performance. I run 100,000 concatenations over two or more 10-char strings. Here are the results on my on a 2.20 GHz Core Duo, 2G RAM Dell notebook running Vista (timings are in milliseconds and are averaged over several runs):

# of operands   Concat    Format
2                  12         60
3                  22         95
4                  28        150
5                 125        160
6                 185        192
7                 210        215
8                 235        239
9                 270        272

To summarise, the Concat method runs 4-5 times faster than Format with 4 operands of fewer, but there is no significant difference with 6 or more operands. It is interesting to notice the steep increase (from 28 to 125 milliseconds) for the Concat method when passing fro 4 to 5 arguments. The reason: there is no String.Concat overload that takes 4 arguments, therefore the VB.NET (and C#) compiler has to build a temporary array with 5 elements and pass it to the String.Concat overload that takes a ParamArray. The same thing happens with the String.Format method when you pass from 3 to 4 arguments.

In general, I prefer to use the String.Format method when appending 3 or more strings, except inside time-critical code. For example, I use it for building error messages and other UI elements; in this case the loss of speed rarely (or never) affects the overall execution speed. Another advantage of String.Format is that you can easily create a table of messages and store them on a database, an XML file, or just a plain text file (possibly stored as a resource). For example, all VB Migration Partner error and warning messages are stored in a format like this:

        Using the '{0}' Windows API method as an argument to the '{1}' function might result in an string filled with spaces. Please split the next line in two statements.

This approach makes the code more readable and maintenable. Plus, localizing the code for a language other than English will be a breeze, if I ever need to.

---------------------------------------

Speaking ofconcatenations, the fact that Concat, Format and other static methods of the System.String class have an overload that takes a ParamArray (and therefore a standard array) makes it possible to implement a few nice tricks. For example, you can quickly concatenate all the elements in an array using this code:

    Dim arr() As String = {"one", "two", "three", "four"}
    Dim res As String = String.Concat(arr)    ' => onetwothreefour

If you need to use a delimiter between each element or concatenate onlya subset of the array, use the String.Join method:

    res = String.Join(" ", arr)         ' => one two three four
    res = String.Join(" ", arr, 1, 2)   ' => two three

When working with arrays of 10 elements or fewer, or you are interested in just the first 10 elements, you can use the Format method in creative ways, as in this code:

    ' display elements in random order
    res = String.Format("{3} {1} {0} {2}", arr)    ' => four two one three 

 


Three new code samples are available

clock January 9, 2008 09:45

Yesterday we added three new samples to our code samples section:

Virtual Night Sky shows how the sky appears at the specified place and time, and provides info on stars under the mouse cursor.
Barcode Generator displays Bar Codes 39 and 128 and lets you copy them to clipboard and save to file.
Grid-Net Waves 3D animates a 3D graph.

What they have in common is that they display graphic output using a combination of VB6 and API methods. VB Migration Partner fully supports both techniques and converts these samples with little or no effort (in all cases you need pragmas to release the device context handle).

I especially like the Virtual Night Sky sample: it took me just two convert-test-fix cycles to have a fully working version, and the entire process took me less than five minutes! I didn't even had to study the source code to understand how it works: the entire process was quite "mechanical", in the sense that I run the VB6 code through the tool a first time to see which compilation errors I got. I added a few pragmas and got a zero-compilation and zero-runtime error VB.NET code, which worked at the first attempt. If only all VB6 applications could be converted so simply!

BTW, this sample showed an interesting (and undocumented, of course) difference between the PictureBox control in VB6 and VB.NET. The original VB6 application allows you to use the Shift+left and Shift+right key combinations to move your point of view on the horizon. Oddly, these key combinations aren't trapped by the VB.NET PictureBox control: instead, they are translated to the Tab and Shift-Tab sequences. In other words, they move the input focus to a different control on the form. We decided NOT to replicate this minor difference in VB Migration Partner's support library: instead, we changed the converted app to use the Alt+left and Alt+right key combinations to achieve the same result. Spotting and fixing this issue took me five more minutes, but I can now explore my night sky with a fully .NET application. 

Note to Beta Testers: if you migrate these three samples with current version 0.92 you'll need to manually fix a few VB.NET statements. These fixes won't be necessary with the upcoming version 0.93.





Follow Francesco Balena on VB6 migration’s group on

LinkedIn


VB Migration Partner - Subscribe to our feed RSS  blog RSS
 
AddThis Feed Button
 
 

Home blog
 
AddThis Social Bookmark Button


 

Sign in

Search

Calendar

<<  September 2019  >>
SuMoTuWeThFrSa
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

Archive

Categories

 

Microsoft Regional Director
Microsoft is a registered trademark of Microsoft Corporation in the United States and other countries and is used under license from Microsoft
 

My programming tools



VB Migration Partner

 

My books


Programming Microsoft Visual Basic 6


Programming Microsoft Visual Basic 2005: The Language


Practical Guidelines and Best Practices for Microsoft Visual Basic .NET and Visual C# Developers