FRANCESCO BALENA ON VB MIGRATION

 

Facebook Fan Page: Francesco Balena on VB6 Migration

clock January 27, 2010 16:32

Alive and kicking.

We just opened Francesco Balena's fan page on Facebook  (yes, FB on FB!), where I'll publish news about VB6 and migration experiences, including useful technical information, tips, whitepapers, and other resources.

The easiest way to always stay up-to-date on VB6, how to convert your legacy code to .NET, and how to recycle your skills in a more modern programming environment.



Pay attention to orphaned ADODB objects

clock September 1, 2009 19:26

Don't take me wrong: COM Interop is a great piece of technology, and Microsoft developers made wonders with it. If you consider how different the COM and .NET worlds are, it's a miracle that they can communicate with each other in such a smooth way.

Actually, COM Interop is such a magic that it's easy to forget that all the COM objects that we manipulate from .NET aren't the "real" COM objects. Instead, they are .NET wrappers that redirect all calls to the actual COM object by means of COM Interop. This fact has many implications. Consider for example this VB6 code:

Function GetRecordCount(ByVal cn As ADODB.Connection, ByVal sql As String) As Integer
   Dim rs As New ADODB.Recordset
   rs.Open sql, cn
   GetRecordCount = rs.RecordCount
End Function

As you see, the Recordset object isn't closed explicitly, but it isn't a problem in VB6 because all COM objects are automatically closed when they are set to Nothing or go out of scope. However, if you translate this code to VB.NET you are in trouble, because nothing happens when the method exits. In other words, you'll have an open recordset hanging somewhere in memory and cause unexplainable errors later in the application. For example, you'll get an error when you'll later try to open another recordset on the same connection.

We know that some customers had to work around this issue by enabling the MARS option with SQL Server, which enables multiple active recordsets on any given connection. This might be a viable solution if you are working with SQL Server 2005 or 2008, but VB Migration Partner offers a better way to handle this case: use the AutoDispose pragmas.

In fact, when this pragma is used, all disposable objects are correctly disposed of when they go out of scope. In this case, VB Migration Partner emits the following code:

Function GetRecordCount(ByVal cn As ADODB.Connection, ByVal sql As String) As Integer
   Dim rs As New ADODB.Recordset
   Try
      rs.Open(sql, cn)
      GetRecordCount = rs.RecordCount
   Finally
      SetNothing6(rs)
   End Try
End Function

where the SetNothing6 helper method takes care of orderly invoking the Close or Dispose method, if necessary.



Dynamic Data Exchange (DDE) can be migrated, at last!

clock July 24, 2009 19:41

If you've never heard of Dynamic Data Exchange - a.k.a. DDE - consider yourself a lucky developer. In short, DDE is a simple communication protocol that allows two or more Windows processes to communicate with each other. DDE never really caught on, however a number of Microsoft applications and languages supported it, including of course Visual Basic (starting with version 3, if I remember correctly).

The main pros of DDE are its simplicity and flexibility: in most cases, you just need to set a few properties and you soon have a number of controls in a DDE client application reflect the state of a control in another application. More precisely, you can have a TextBox, Label, or PictureBox in an application (known as the DDE client) to be "linked" to a similar control in another application (known as the DDE server). You can even have multiple clients linked to the same server application, so that when the control in the server app changes, all the client apps automatically receive the new value. The DDE protocol allows other variations on this theme, for example the code in the client app can poke a value into a server's control, so that a client can indirectly notify all other clients that a new value is available.

DDE has several shortcomings, too, which explain why it never became very popular. For one, it appears to be quite fragile and you have no warranty that all the processes involved in the communication received the notification. For these reasons, it was soon replaced by COM in all developers' minds and hearths.

Regardless of what your opinion about DDE is, it's a fact that many VB6 applications - especially those that were born as VB3 projects - do rely on DDE. In the last few months we received several enqueries from potential customers who needed to migrate a VB6 app that was based on DDE to some extent. In these cases we could only suggest to replace DDE code with COM code before the migration, or replace portions of the VB.NET code with calls to native .NET technologies, such as remoting or WCF.

This is what all articles about migration recommend and is also what all our competitors do, but clearly wasn't what our customers liked to hear, especially because implementing a robust communication mechanism based on .NET requires time and skilled developers, plus the time required to fix the migrated VB.NET code to use the new mechanism.

About one month ago we rolled up our sleeves and we are now proud to announce the VB Migration Partner now supports all DDE properties, methods, and events!

Instead of implementing the "real" DDE protocol - an approach that would inherit all the DDE limitations and weaknesses - we decided to adopt a different approach based on Windows messages. This gives "our" DDE more robustness and better efficiency, while keeping it source-code-compatible with all existing DDE code written in VB6. In other words, we do support all the DDE properties and methods even if we internally use a different implementation.

Because of the approach we chosen, there are only a few minor differences with the "real" DDE protocol. The main limitation is that a migrated VB.NET app can use DDE only to communicate with another migrated VB.NET app. (If you used DDE to communicate with Microsoft Excel you can't use our prioprietary implementation, for example.) Also, our DDE protocol is inherently synchronous, therefore we don't support the LinkTimeout property and the LinkError event. Except for these details, the implementation is complete and sound. If your VB6 app uses DDE, this single feature can save you weeks during the migration process.

If you are interested in seeing a migrated VB.NET app that uses DDE, you can download a good sample here. The sample includes two projects - the client and the server - so you should launch the DDE server first and then one or more instances of the DDE client application. You will see that changing a field in the server app affects the linked control in all clients. You can compare the VB.NET behavior with the original VB6 application, whose source code are available here.

It almost goes without saying the VB Migration Partner is the FIRST and ONLY migration software that supports DDE, an achievement that confirms that our product is simply the state-of-the-art in its market segment.



Can your VB6 conversion tool handle null propagation?

clock May 21, 2009 03:13

If you have massively used Variants in your VB6 apps, the migration to .NET might be a nightmare. The best that the Upgrade Wizard and other tools can do is translating each “As Variant” into “As Object”, on the assumption that the Object type can do everything that a Variant does.

You shouldn’t be surprised to learn that this is one of the false migration myths, or is a gross oversimplification of the truth, to say the least. In fact, the .NET Object type lacks what arguably is the most useful feature of Variants: support for Null values and Null propagation in expressions.

Under VB6 a Variant variable can hold the special Null value and – even more important – all the string and math expression that involve a Null operand also deliver a Null Variant value. Virtually all database-intensive applications deal with Null values and have used null propagation to an extent. Now, let’s consider this VB6 code:

' rs is an ADODB.Recordset
Dim realPrice As Variant
realPrice = rs("Price")
realPrice = realPrice * (100 – rs("Discount")) / 100
If Not IsNull(realPrice) Then DisplayPrice(realPrice)

Under VB6, if either the Price or the Discount field is Null, the realPrice variable is assigned Null and the DisplayPrice method isn’t invoked. Now, consider the “canonical” VB.NET version of the above code:

Dim realPrice As Object
realPrice = rs.Fields("Price").Value
realPrice = realPrice * (100 – rs.Fields("Discount").Value) / 100
If Not IsNull(realPrice) Then DisplayPrice(realPrice)

Under .NET a Null database field returns a DBNull object – more precisely, the DBNull.Value element. The problem is, no math or string operation is defined for the DBNull type, something I consider one of the most serious mistakes Microsoft made in this area. As a result, the third statement throws an exception if either field is Null. So long, functional equivalence! Welcome, migration head-aches!

Null propagation is a serious problem not to be underestimated. One of our customers has found that, on the average, one out of 40 statements uses Variant expression and relies on null propagation. To put things in the right perspective, in a middle-sized real-world application you can expect to manually fix several thousand statements that use null propagation. What’s worse, there is no simple way to work around Null values in VB.NET, short of splitting all statements in portion and testing each and every subexpression for the DBNull.Value value.

If you use VB Migration Partner, you can solve the above problem in the most elegant and painless way. Just use the following project-level pragmas:

 '## project:NullSupport
 '## project:ChangeType Variant, VB6Variant

The NullSupport pragma tells VB Migration Partner to map some VB library functions – such as Left, Mid, Right, and a few others – to special methods that are defined in VB Migration Partner’s support library and that, not surprisingly, correctly return a Null value if their argument is Null (as they do under VB6).

The ChangeType pragma converts all Variant members – variable, fields, properties, methods, etc. – as the special VB6Variant type (also defined in VB Migration Partner’s support library) which redefines all the math, comparison, string, and logical operators to support null values.

Yes, this is all you need to add null propagation support to your VB.NET code! This feature alone can save you literally weeks in a real-world migration project. And don’t forget that VB Migration Partner is the only VB6 conversion software that supports Null values and Null propagation easily and automatically.

When you hear other vendors claiming that “our tool uses advanced artificial-intelligence-based techniques to ensure functional equivalence with the original VB6 code”, just ask them how it deals with Null values. Smile



All the white papers in one place

clock February 21, 2009 21:57

We just put online a new page that gathers all the whitepapers we have published so far, and the ones that will be in the future.

If you need guidance when selecting a conversion tool or a migration strategy, this is the place to go.



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

clock February 10, 2009 03: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



[DOC] Differences between VB6 and .NET controls

clock January 22, 2009 23:30

In these three years we at Code Architects work very hard to study, discover, analyze, dissect, and devise workarounds for the huge set of differences between VB6 and VB.NET.

We put all this knowledge into our VB Migration Partner, of course. But we thought it was fair to make this information available to the many VB6 developers who still have one or more apps to convert, regardless of whether they would buy our software.

Yesterday we more than doubled the amount of material freely available on vbmigration.com, and in fact we decided to split this knowledge in two pages:

The control page currently lists the differences between VB6 built-in controls (forms, textboxes, labels, buttons, etc.) and their Windows Forms counterparts. We plan to cover the remaining controls - such Windows Common Controls, RichTextBox, etc. - in the near future. Stay tuned!


VB Migration Partner vs. manual porting from VB6

clock January 19, 2009 20:31

More and more VB6 and VB.NET developers are discovering VB Migration Partner and are realizing that it *is* possible to convert VB6 legacy apps in a fraction of the time (and money) that is required when following other approaches or using other tools. However, a casual visit to VB6/VB.NET forums shows that many false myths and misconceptions about migration are still strong.

For this reason, every now and then I like to partecipate to these discussion and throw in my own point of view. I think it makes sense to republish it here. The following text is adapted from a longer comment I posted toa thread entitled Conversion from VB6 to VB.NET 2005 on DevX Forums.


 

I believe that the confusion about automatic code translators raises because most people expect that they work magically without any human intervention. Alas, this is the approach used by the Upgrade Wizard inside VS and this explains why it performs so poorly. In most real-world cases you can't simply run the tool on a piece of contorted VB6 code and have fully working VB.NET code at the first attempt. Instead, a real-world migration process is an iterative task.

A code translator should migrate your code according to *your* personal style and preferences. VB Migration Partner allows this by means of so-called "migration pragmas", which are special remarks that you embed in the original VB6 code. For example, this pragma tells VBMP that special code must be emitted so that the VB.NET can correctly handle default members even in late-bound mode (which is one of the main limitations of Upgrade Wizard):
    '## project: DefaultMemberSupport True
VBMP supports 60+ different pragmas to automate virtually every adjustment that might be needed after an initial quick-and-dirty migration. Pragmas are essential because they allow you to repeat the migration many times and still keep the VB6 and VB.NET code in sync, even if the migration process takes months.

VB Migration Partner can handle many recurring migration issues quite nicely. In most cases it can do it without human assistance, in the worst case it requires that you add a pragma.

Quoting a previous forum comment: "If you are crazy enough to spend 3 months to make a conversion work with a loss of performance compare to the VB6 application, while a 2 months rewrite would do the job and give you something that easier to maintain, go ahead."

With small projects this approach can make sense, provided that you can afford the extra time/cost and that you have a team that is adequately skilled in both VB6 and VB.NET. One of our customers is tackling one "monster" app of about 15 million lines scattered nearly 1500+ VB6 projects; the customer has estimated that it would take about 20 years/man to rewrite it from scratch, which they can't afford. They also estimated that VB Migration Partner will cut cost and time-to-market by at least 70-75%, which made their choice obvious. [NOTE: it later became clear that the actual savings was closer to 85-90%. The migration should be completed in a few months.]

The real value of a good converter such as ours is that an individual developer (or a team) can handle the migration process even if they aren't expert in either the source or the target language. VB Migration Partner contains a huge database of "good programming" rules as well as "obscure bugs" that are unknown to most developers, including a few expert ones. Let me make one simple example:

    Dim buffer As String
    buffer = Space(256)
    ' GetSystemDirectory API returns # of chars in the result string
    buffer = Left(buffer, GetSystemDirectory(buffer, 256))

This code works well in VB6 but delivers bogus results when translated as-is to VB.NET. VB Migration Partner correctly points to the issue and its solution. The question is: How many VB developers are aware of this subtle difference between the two languages? You might object that this code isn't representative of good VB programming, yet there are tons of code like it out there, and most "human converter" aren't skilled enough to find and fix it. (Here I talk from personal experience: it took hours to track down the problem the first time we bumped into it.)

Let me make one more example. In VB6 the default parameter passing mechanism is ByRef, which means that most methods take a byref argument only because the original developer forgot to add an explicit ByVal keyword. The problem is, VB.NET should use ByVal whenever possible because this coding style prevents a lot of compile and runtime errors. (You can have problems when passing an ADODB object to a byref parameter, and there are many other similar cases)

A developer doing the port or the rewrite, therefore, should carefully check each byref parameter and see whether it can be converted into ByVal without changing the code behavior, but this task takes days on a real-world app, because you'd have to check whether *each* parameter is modified, or passed to a method that modifies it, and so on recursively. VB Migration Partner does it automatically in a matter of seconds, emits a warning, and can optionally convert the ByRef into ByVal for you.

The code analysis engine in VB Migration Partner can do many other interesting things. It can remove unused methods, Declare, and Const statements; replace Exit Function with Return, merge DIM and assignments (eg Dim x As Integer = 1), suggest when a StringBuilder object could replace a set of concatenations, and so forth. Once again, all refactoring actions are optional and can be controlled via pragmas. An average developer can surely perform these task manually, but in practice it happens quite seldom because he or she can hardly afford the extra time that these activities require.

I could continue with more examples of how an automatic code converter can do a better job than developers. Not in all cases, but in a large percentage at least. If you are interested, you can find all docs, KB articles, videos, and code samples at www.vbmigration.com. (We provide both the original VB6 code and the resulting VB.NET code.)

As far as I know, we are the only migration tool authors who dare to expose all this information to the public *before* they purchase the product. I hope potential customers or just programmers who are curious about conversion technology will appreciate this "transparency" policy.



Subtle array cloning issues

clock October 5, 2008 04:46

As you may know, VB6 performs a copy of the array when assigning an array to another array variable. After the assignment, the source and destination arrays are distinct objects, as this code demonstrates:

Dim source(10) As Integer
source(1) = 111
Dim dest() As Integer
dest() = source()
Debug.Print source(1), dest(1)    ' displays "111,111"
' changing an element in either array doesn’t affect the other
source(1) = 222
Debug.Print source(1), dest(1)    ' displays "222,111"

Conversely, an array assignment under VB.NET just copies the array address:, the source and destination array variables point to the same set of elements. In other words, changing an element through either variable would affect the value seen by the other array variable.

For these reasons, both the Upgrade Wizard and earlier versions of VB Migration Partner automatically force a copy operation when converting an array assignment. This is how Upgrade Wizard converts the above VB6 code snippet:

Dim source(10) As Integer
source(1) = 111
Dim dest() As Integer  
dest = source.Clone()
Debug.Print source(1), dest(1)    ' displays "111,111"
' changing an element in either array doesn’t affect the other
source(1) = 222
Debug.Print source(1), dest(1)    ' displays "222,111"

It seems that adding a call to the Clone method solves the problem once and for all, but unfortunately this isn’t the case. As a matter of fact, there are several cases when the Upgrade Wizard (and probably other VB6-to-VB.NET conversion tools) doesn’t preserve functional equivalence with the original VB6 code.

For startes, a plain Clone method doesn't work correctly if the array you are assigning is an array of arrays (Under VB6, an array of arrays is a Variant array whose individual elements are arrays.) The problem here is that the Array.Clone method performs a shallow copy and doesn’t correctly copy nested arrays and the converted VB.NET application doesn’t work as it should. For this reason, starting with version 1.10, VB Migration Partner supports the CloneArray6 helper method, which can perform both a shallow copy and a deep copy (if the second argument is True, also nested arrays are copied):

   Dim arrayOfArrays() As Variant
    ' …
    Dim dest() As Variant
    dest() = CloneArray6(arrayOfArrays(), True)

The CloneArray6 method is available both in VB6 and VB.NET, but the VB6 version is a do-nothing version and doesn't change the application behavior. The CloneArray6 method can correctly handle arrays of arrays of any nesting level, arrays of structures, and more in general arrays of any ICloneable object.

Another case when UpgradeWizard generates bugged VB.NET code is when assigning a structure that contain one or more arrays. Consider this VB.NET code, generated by Upgrade Wizard when converting an assignment between two structures:

Structure MyUDT
    Public arr() As Integer
End Structure

Sub Test()
    Dim udt As MyUDT
    Dim udt2 As MyUDT
    udt2 = udt
    ' udt and udt2 point to different structures, but
    ' they share the same set of array elements
    ' …
End Sub

It is obvious that the assignment between structures doesn’t work as in the original VB6 code, because the udt and udt2 structures are pointing to the same array: changing one element in the array in one structure affects the array pointed to by the other structure.Unlike Upgrade Wizard and other conversion tools, VB Migration Partner works around this issue by rendering the assignment as follows:

Structure MyUDT
    Public arr() As Integer

    Function Clone() As MyUDT
        Dim copy As MyUDT = Me
        copy.arr = CloneArray6(Me.arr)
       Return copy
    End Function

End Structure

Sub Test()
    Dim udt As MyUDT
    Dim udt2 As MyUDT
    udt2 = udt.Clone()
    ' udt and udt2 are now completely disjoint objects, and share no arrays
    ' …
End Sub

You might bump into another elusive bug when invoking methods that store a reference to the array passed as an argument. The simplest example of this problem is when you store an array into multiple items of a Collection, as this VB6 code demonstrates:

Dim col As New Collection
Dim values(1) As String
values(0) = "Francesco": values(1) = "Balena"
col.Add(values)
' notice that here we reuse the same array…
values(0) = "Giuseppe": values(1) = "Dimauro"
col.Add(values)
Debug.Print col(1)(0) & "," & col(2)(0)   ' => "Francesco,Giuseppe"

The Upgrade Wizard converts this code verbatim, but the resulting VB.NET code just doesn’t work as expected: both col(1) and col(2) elements actually point to the same array, therefore the text displayed in the Output window is not the one you hoped for:

Debug.WriteLine(col(1)(0) & "," & col(2)(0))  ' => "Giuseppe,Giuseppe"

A solution to this problem is to explicitly ReDim the array after each Add method, as follows:

Dim col As New Collection
Dim values(1) As String
values(0) = "Francesco": values(1) = "Balena"
col.Add(values)
' allocate a new memory block for array elements
ReDim values(1)
values(0) = "Giuseppe": values(1) = "Dimauro"
col.Add(values)

However, this workaround adds unnecessary overhead to the VB6 code. Starting with VB Migration Partner version 1.10 you can avoid this overhead and have the most efficient behavior in both VB6 and VB.NET by using the CloneArray6 method:

Dim col As New Collection
Dim values(1) As String
values(0) = "Francesco": values(1) = "Balena"
col.Add(CloneArray6(values))
values(0) = "Giuseppe": values(1) = "Dimauro"
col.Add(CloneArray6(values))

The bottom line: pay close attention to how arrays are assigned and copied in your VB.NET project and carefully scrutinize the code that your VB6-to-VB.NET migration tool generates in these cases. Else you might be spending hours trying to track down these elusive bugs.



A better cure for a common VB6 misconception

clock October 2, 2008 20:48

In an article I wrote about two months ago I showed how you can fix a programming mistake that is quite common among VB6 developers, who tend to believe that the following declaration

     Dim x1, y1, x2, y2 As Integer

declares four integer variables, whereas it actually declares only one integer (y2) and three Variant variables. The solution I proposed required one PreProcess pragma for statements declaring two variables, another PreProcess pragma for statements declaring three variables, and so forth.

Yesterday Marco Giampetruzzi - from the VB Migration Partner Team - surprised me with a better solution that uses just one pragma to account for all Dim, Public, Private, and Static statements, regardless of the number of variables they declare:

    '## PreProcess "(?<=\b(Dim|Public|Private|Static)\b.*)
                    (?<!\b(Sub|Function|Property)\b.*)
                    (?<!As\s+)(?<var>\b\w+\b)(?=,.*\bAs\s+(?<type>\w+))",
                    "${var} As ${type}", True

Let me briefly explain how it works. The first regex tells VB Migration Partner to search an identifier and name it as "var" (?<var>\w+). This identifier must preceded on the same line by one of the declaration keywords (?<=\b(Dim|Public|Private|Static)\b.*). To prevent that parameters on a method declaration be mistakenly matched, we also check that the identifier not be preceded by a keyword used in method and property declarations (?<!\b(Sub|Function|Property)\b.*). Next, the regex ensures that the indentifier be immediately followed by a comma and (after some characters) by an As clause (=?,.*\bAs\b\s+(?<type>\w+)). Notice that above pragma doesn't support array declarations.

Very smart indeed... thanks Marco!

You can build similar PreProcess pragmas to account for similar cases. For example, the following pragma adds an As clause to parameters inside method declarations:

    '## PreProcess "(?<=\b(Sub|Function|Property)\b.*)
                    (?<!As\s+)(?<var>\b\w+\b)(?=,.*\bAs\s+(?<type>\w+))",
                    "${var} As ${type}", True

If you aren't familiar with regexes, drop a line to our tech support and we'll cook one for you!




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

Home blog
 
AddThis Social Bookmark Button


 

Sign in

Search

Calendar

<<  March 2010  >>
SuMoTuWeThFrSa
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910

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