FRANCESCO BALENA ON VB MIGRATION

 

Neat tricks for smooth migration of calls to Windows API methods

clock June 12, 2008 21: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. (Enterprise Edition only)
  • 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 (Enterprise Edition only)
  • 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!

Currently rated 4.7 by 3 people

  • Currently 4.666667/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


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

clock February 20, 2008 06: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 

 

Currently rated 4.0 by 2 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Speed up string concatenations after the migration from VB6

clock December 21, 2007 21:41

When you migrate code from VB6 - regardless of whether you are using VB Migration Partner, the Upgrade Wizard, or another automatic conversion tool - chances are that string-intensive code will actually run slower under VB.NET, if it uses a lot of string concatenation operations. For example, this code takes 2.8 seconds when it runs in VB6 and 27 seconds after its conversion to VB.NET on my 3GHz system:

Dim s As String = ""
For i As Integer = 1 To 100000
    s = s + "*"
Next

The solution is of course trivial: just replace the string variable with a StringBuilder object. Alas, this fix requires that you completely revise the VB.NET code, because you need to replace all + and & operators with the Append method, not to mention cases where the StringBuilder is used as an argument to string functions such as Trim or Len.

Is there a way to speed up the previous code with a minimal impact on the code itself? The answer is yes and the solution is actually very simple: you just need to create a VB.NET class that is based on the System.Text.StringBuilder object, that redifines the + and & operators, and that supports the implicit conversion to and from the System.String type. Authoring such a StringBuilder6 class is a matter of minutes:

' a wrapper for the StringBuilder object, with support for + and & operators

Public Class StringBuilder6

    Private buffer As New System.Text.StringBuilder

    ' return the inner string

    Public Overrides Function ToString() As String
       
Return buffer.ToString()
    End Function

    Public Shared Operator +(ByVal op1 As StringBuilder6, ByVal op2 As String) As StringBuilder6
        op1.buffer.Append(op2)
       
Return op1
    End Operator

    Public Shared Operator &(ByVal op1 As StringBuilder6, ByVal op2 As String) As StringBuilder6
        op1.buffer.Append(op2)
        Return op1
    End Operator

    ' convert to string

    Public Shared Widening Operator CType(ByVal op As StringBuilder6) As String
       
Return op.ToString()
    End Operator

    ' convert from string

    Public Shared Widening Operator CType(ByVal str As String) As StringBuilder6
        Dim op As New StringBuilder6()
        op.buffer.Append(str)
        Return op
    End Operator

End Class

Once the StringBuilder6 class is in place, you just need to replace the type of the String variable:

Dim s As StringBuilder6 = ""

After this edit, the loop runs in 0.008 seconds, that is about 2000 times faster!!! Not bad, for such a simple fix :-)

The StringBuilder6 class is so useful that we decided to include it in the forthcoming version 0.92 of VB Migration Partner. Even better, you can use a SetType pragma right in the original VB6 code to tell our tool that a given string variable must be rendered with this new type:

'## s.SetType StringBuilder6
Dim s As String = ""
For i As Integer = 1 To 100000
    s = s + "*"
Next

 

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


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

Home blog
 
AddThis Social Bookmark Button


 

Sign in

Search

Calendar

<<  July 2008  >>
SuMoTuWeThFrSa
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

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 Blogs


My programming tools



VB Migration Partner


VBMaximizer for VB6


Form Maximizer for .NET

 

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