Francesco's blog

 Friday, January 11, 2008

Many readers asked whether I was about to release a book about VB 2008 and, more in general, how come I have reduced my writing activity so dramatically. The answer is in this post.

Immediately after publishing my C# 2005 book I started working on a very ambitious software project. Now, after exactly two years of hard work, VB Migration Partner is in beta test stage and is quickly approaching the public release.

The name of this tool says it all: in a nutshell, it is a converter of VB6 applications to VB.NET. I won’t waste your time trying to emphasize how weak the Upgrade Wizard that comes with Visual Studio is: if you’ve never tried it out, just google for “vb6 migration” and read what developers and bloggers have to say about it. Our goal at Code Architects was to build a better mouse trap than that.

A word on the migration-vs-rewriting debate: Many industry experts suggest that you should rewrite your VB6 from scratch rather than trying to port it to .NET automatically using a conversion tool, because the two environments are just too different. In theory this argument makes a lot of sense; in practice, however, when you have a real-world business app with some hundreds thousands lines of code – or even millions of lines! – a rewrite from scratch is simply too expensive, especially when the developers who wrote it originally aren’t working with the company any longer, the documentation is poor, remarks are scarce, etc. When facing the perspective of investing many years/man in this daunting task, it’s no surprise that many companies have postponed the migration to “some other time.” Now they are realizing, however, that the migration step can’t be postponed any longer, either because a few VB6 apps don’t work under Microsoft Vista – possibly because they use 3rd-party controls that are incompatible with the new Microsoft operating system – or, above all, because Microsoft declared that VB6 support ends on March 2008.

The problem is, automatic code converters don’t have a good reputation and rarely keep their promises, regardless of the language you are migrating from/to. In most cases, they manage to convert 95% of existing code, or less. With a 100,000 line application – a medium-size application of average complexity, then – having to manually fix the remaining 5% means that you have to fix about five thousand lines. It doesn’t sound too bad, until you realize that most of these “fixes” might take hours or even days. For example, consider that VB6 and VB.NET have two vastly different (and incompatible) programming models for drag-and-drop. Therefore one OLEDrag method counts as an individual problematic statement, yet it forces you to rewrite a large portion of your code. Drag-and-drop isn’t the only example, because the same concept applies to graphic statements, printing, data-binding, object finalization, component initialization, and many others.

We tested our VB Migration Partner on hundreds of VB6 apps – for well over one million lines – and found out that it delivers code that compiles and runs correctly most of the times, with an average of one compilation error every 1,100 lines of code. This corresponds to an accuracy higher than 99.9%. Not bad, uh? Of course the actual accuracy depends on how well the VB6 code is and on the features that the application uses, thus you can get a better idea of how good our tool is by having a look at the VB6 features that itsupports ...and that are out of reach for most other conversion tool on the market:

  • arrays with nonzero LBound
  • Gosub, On...Goto, and On ...Gosub
  • auto-instancing variables (Dim x As New Person), which keep their lazy-instantiation feature even under VB.NET
  • As Any parameters and callback addresses in Declare parameters (e.g. EnumWindows)
  • default properties resolved correctly even for late-bound variables
  • deterministic finalization for objects such as Connection and Recordset, to ensure that the connection is correctly closed when the variable is set to Nothing or the current scope is exited
  • (limited) support for Variant variable and null propagation in expressions
  • all 60+ controls in the VB6 toolbox, with the only exceptions of OLE container and Repeater, plus the support for common ActiveX controls and components such as WebBrowser, ScriptControl, Scripting runtime, and the MSWLess library
  • control arrays, including arrays of 3-rd party controls
  • popup menus
  • the Controls.Add method and the VBControlExtender object, to dynamically create data-entry forms
  • graphic methods (Line, Circle, PSet, PaintPicture, etc.), with support for any ScaleMode, including custom user modes
  • the Printer object and the Printers collection
  • OLE drag-and-drop
  • UserControl classes, with support for advanced features such as the Ambient and Extender objects
  • data-binding to DAO, RDO, and ADO Data controls, ADO recordsets, DataEnvironment objects
  • non-hierarchical DataEnvironment objects
  • ADO data source and simple data consumer classes and user controls (e.g. custom ADO Data controls)
  • MultiUse, PublicNotCreatable, GlobalMultiUse classes
  • persistable classes, MTS/COM+ classes

The number of supported features is so high that it is simpler to list the features that are not supported: UserDocument, PropertyPage, WebClass and DHTML Page components; undocumented methods (VarPtr, ObjPtr, StrPtr); “classic” drag-and-drop, and little else.

VB Migration Partner can convert an entire VB6 project group in one operation, in which case it typically delivers better quality code because it can see “inside” a DLL that belongs to the group. The tool also includes a very sophisticated code analyzer that can spot unused members: a large project that has evolved over many years can easily include 5-10% of “dead code”, thus this feature alone can save you a lot of precious time because it allows you to focus on just the code that is really important.

One of the secrets for such high error-free conversion factor is the support for migration pragmas. A migration pragma is a special remark that you can add to the original VB6 code and that teaches VB Migration Partner how a given portion of code must be translated, if many alternatives exist. For example, the following ArrayBounds pragma tells VB Migration Partner that all the arrays in the current projects must have their lower bound index forced to zero:
          '##project:ArrayBounds ForceZero
Pragmas can have project, class, method, or variable scope. A pragma scoped at the variable- or method-level always have higher priority than pragmas scoped at the project- or class-level. For example, you can override the previous pragma inside a method, and then override this latter pragma for a given array variable:
          Sub Test()
             '## ArrayBounds Shift
             '## arr.ArrayBounds ForceZero
             '## names.ShiftIndexes 1
             Dim names(1 To 10) As String
             Dim values(1 To 10) As Long
             Dim arr(1 To 10) As Integer
             names(1) = "John": names(2) = "Ann"
             '...
          End Sub

The Shift option tells VB Migration Partner that the LBound and UBound indexes must be shifted towards zero, so that the total number of elements in the array is preserved. The ShiftIndexes pragma –applied only to the names array – ensures that constant indexes are correctly shifted too:
          Sub Test()
             Dim names(9) As String
             Dim values(9) As Integer
             Dim arr(0 To 10) As Short
             names(0) = "John": names(1) = "Ann"
          End Sub

VB Migration Partner supports over 50 pragmas. For example, the SetName pragma changes the name of a control or variable during the migration, SetType changes its type, AutoNew implements the auto-instancing behavior of As New variables, AutoDispose implements the deterministic finalization when the variable is set to Nothing or the current scope is exited, and so forth.

One of the main defects of a “traditional” conversion tool is that – after the first migration – there is no relationship between the original VB6 project and the new VB.NET project. If the migration process takes weeks or months – which is common for large projects – and if the VB6 application must evolve in the meantime with new features and bug fixing, then at the end of the migration process the VB.NET code is already outdated and must be manually patched. The margin for mistakes in this phase is very high.

VB Migration Partner allows you to work around this issue, by means of pragmas and what we call the convert-text-fix cycle methodology. In a nutshell, you can iteratively migrate a given VB6 project by reaching the zero compilation errors stage and then the zero runtime error stage by simply inserting pragmas in the original project, but without modifying any VB6 executable statement. Thanks to the convert-test-fix cycle you can face complex migration tasks and keep the VB6 and VB.NET versions of your app always in sync.

I am especially proud of VB Migration Partner’s refactoring engine, which is able to apply many optimization techniques the result of the “raw” conversion. For example, it can merge variable declarations and the initializations, to generate Return statements, and to leverage the compound assignment operators (e.g. +=), and more:

    Function GetValue() As Long     =>     Function GetValue() As Integer
       Dim x As Long                          Dim x As Integer = 123
       x = 123                                ...
       ...                                    If x > 0 Then
       If x > 0 Then                             Return x
          GetValue = x                        ElseIf x = 0 Then
          Exit Function                          Return -1
       ElseIf x = 0 Then                      End If
          GetValue = -1                       ...
          Exit Function                       x += 1
       End If                                 ...
       ...                                 End Function
       x = x + 1
       ...
    End Function

Other refactoring techniques can be achieved by means of pragmas. For example, you can move the declaration of a variable inside a For or For Each method, as in this example:

    Dim i As Integer                =>     For i As Integer = 1 To 100
    For i = 1 To 100                          ...
       ...                                 Next
    Next

On top of the cake: in spite of all these features, VB Migration Partner is up to 8 times faster than the Upgrade Wizard that comes with Visual Studio!

You can learn all about VB Migration Partner on our new site www.vbmigration.com. The web site hosts a Resource section where we dissect each and every difference between VB6 and VB.NET – most of which have never been documented anywhere – thus you can find many useful tips even if you don’t plan to use our tool. I also opened a new blog, entirely devoted to migration techniques.

That's it for now. Stay tuned!

1/11/2008 11:00:33 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [11]  | 
 Saturday, December 22, 2007

When you migrate code from VB6 - regardless of whether you are using an 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 your source 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 :-)

Regardless of whether you are migrating code from VB6 or you've written VB.NET or C# code from scratch, the StringBuilder class gives you a quick and simple way to check whether your string concatenations can be optimized by resorting to a StringBuilder.

 

12/22/2007 11:40:06 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [3]  | 
 Wednesday, April 11, 2007

I like the ability to extend the power of my applications by simply adding a reference to an assembly that contains the functions or the controls that I need. I like much less, however, the need to distribute and deploy many DLLs together with my executables. In this post I show a technique that I use to compress (nearly) all the DLLs of a Windows Forms application and "merge" them with the main EXE.

All the files you need are in this ZIP archive, which contains the AsmZip.exe utility (which you run from the command prompt) and two source files, Unzipper.cs and Unzipper.vb. I suggest that you copy the AsmZip utility in a directory listed on the system path, to run it easily.

Step-by-step
These are the steps you must follow to implement the technique.

1) Add either the Unzipper.vb or the Unzipper.cs file to the main project of your application, depending on the language you've used.

2) In the Main method, add a statement that initializes the AssemblyUnzipper class (which is defined in the Unzipper file you added in step 1).
       ' (Visual Basic 2005)
       CodeArchitects.AssemblyUnzipper.Initialize()
       // Visual C# 2005
       CodeArchitects.AssemblyUnzipper.Initialize();
It is essential that this statement runs before any other statement in the application, particularly before showing a form that contains a control implemented in one of the DLLs you want to compress. If you are working with VB and the application has a startup form (and therefore you don't have a Sub Main method), you should initialize the AssemblyUnzipper class from inside the startup form's static constructor:
      Shared Sub New()
         CodeArchitects.AssemblyUnzipper.Initialize()
      End Sub

3) compile the project, obviously in Release mode. (You should use this technique just before delivering the executable to your customer(s).

4) open a command prompt window from inside the application's \bin directory, and run the AsmZip utility as follows:
             AsmZip main.exe *.dll
where main.exe is the name of the main executable. The above command compresses *all* the DLLs in the directory and appends the compressed data to the main.exe file. If you want to compress just a subset of the DLLs that the application uses, you should specify their names, as in this example:
             AsmZip main.exe CodeArchitects*.dll Microsoft*.dll
There can be a few good reasons not to compress some of the DLLs used by the applications, as I'll explain shortly.

5) You can now delete all the DLLs that you have compressed, because the application - thanks to the AssemblyUnzipper class - is able to find them at the end of its executable file, to decompress them, and to load them in memory.

Pros
Before proceeding with an explaination of the technique's inner details, let's summarize its advantages:

a) simpliefied deployment: you need to distribute fewer files (often just the main EXE)
b) more robust applications: end users can't break the application by accidentally deleting one of its DLLs
c) fewer bytes on disk: all DLLs are compressed and appended to the main EXE file
d) the ability to "hide" some of your trade secrets, for example which 3rd party controls you've used
e) a slightly better protection of your intellectual property: compressed DLLs can't be decompiled, at least not as easily as uncompressed DLLs .

The last two points aren't a real protection against even unexperienced malicious hackers, if he or she is determined to peek into your application. To do so he would just need to decompile the main EXE, understand how the AssemblyZipper class works, and write a short programma that works similarly but saves the uncompressed assemblies to disk. In other words, don't rely on this technique to protect your code from reverse engineering.

The AsmZip tool relies on the GZipStream class to compress the original DLLs, therefore the compression factor that you achieve with this technique is lower than the one you can obtain with WinZip or WinRar, but it is usually more than adequate, as the following figure shows.


How it works
This technique relies on the AssemblyResolve event of the AppDomain object. This event fires when the CLR loads an assembly referenced by the running application. By handling this event you can perform some nice tricks that wouldn't be possible otherwise. For example, you might load satellite assemblies from a network share or from a binary field in a database.

The AssemblyUnzippere class uses this event to search the required assembly from a compressed stream that has been appended to the application's main EXE file:
      // the handler for AssemblyResolve event
      static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e)
      {
         // find the assembly with given name, cause error if not found
         AssemblyInfo info = null;
         if ( AsmInfos.TryGetValue(e.Name, out info) )
            return ExtractAssembly(info);
         // signal error
         Debug.WriteLine("Failed to uncompress assembly " + info.Name);
         return null;
      }
Each AssemblyInfo object keeps track of where, in the main EXE file, the compressed data for each DLL is located. The AsmInfos dictionary enables the code to quickly locate the information associated with a DLL with given name. This dictionary is created inside the Initialize method, when the application is launched, and is then used each time the application attempts to load an assembly. For more details, see comments in either the VB or the C# source code.

Limitations
I tried this technique with several Windows Forms apps, without any problem. The main issue is that compressed assemblies loaded programmatically have their Location property set to null/Nothing, but if you don't use reflection to explore the assembly's feature you might never realize that the assembly was loaded in a nonstandard way. For example, if your app dynamically loads all the assemblies in a given directory, for example to explore their attributes, it is evident that it won't work as intended if these DLLs have been compressed and then deleted. In such cases, you should exclude these DLLs from compression.

The AssemblyZipper class works only with Windows Forms applications. For what I know, it is possible to use the AssemblyResolve event inside ASP.NET applications, but it isn't possible to use the AssemblyUnzipper in that context. However, the problems that this technique solves aren't considered as real issues under ASP.NET, therefore I don't think it makes sense to use it in web applications.

The only other limitation is that this technique works with DLLs but not with the main EXE. If you have a large EXE that uses some small DLLs, you won't achieve an interesting compression factor. In such a case, you might want to move your forms from the main EXE into a DLL and then compress the DLL with AsmZip. Even better, the main EXE might contain only the splash screen (if you have one) and it should load the startup form from the DLL that contains the actual application. Using this approach it is often possible to achieve an overall compression factor near or above 60 percent.

Note: in the first implementation of this technique I managed to successfully compress even the main EXE and used a small “stub” executable whose only job was to decompress and launch the actual EXE. After some tests, however, I found that the technique wasn’t very stable and I fell back to the technique described in this article.

4/11/2007 5:07:41 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [2]  | 
 Tuesday, August 29, 2006

I know, I know, there are soooo many regular expression tester tools available on the 'Net, but I couldn't help creating my own. It's very simple, yet it supports all the basic features you'd espect from a tool of its kind, including code generation (VB and C#) and compilation to stand-alone assemblies. Best of all, it comes with source code. You can download it from the home page of my Visual Basic 2005 book (together with all other code samples in the book) or directly from here:

Executable (requires .NET Framework 2.0): RegexTester.zip (75.57 KB)
Source code (VB2005): RegexTester source.zip (88.15 KB)

Using the tool is quite simple. The main window is divided in three panes: (a) the pane where you enter the regex, (b) the pane where you load the text the regex must be applied to, (c) the result pane. A fourth pane appears when you select the Replace item from the Commands menu, and it's where you enter the replace pattern. As you can see in the image below, you can enter most regex patterns by selecting them from the context menu:

You select the kind of command (Find, replace, Split) from the Commands menu and you select one or more regex options from the Options menu:

After selecting the proper options, you press the F5 key (the Run item in the Commands menu) to execute the regex. Results are displayed in the bottom pane in a variety of formats and sort orders, which you can select from the Results menu, and the status bar displays the number of matches, the execution time, and the properties of the result currently highlighted in the result pane:

Alternatively, you can set all these options from the Properties dialog box (the Properties command in the File menu, or just press the F4 key):

Assigning a name to the current regex is important because you can save it on disk in a file with .regex extension, for later retrieval.

The Commands menu contains a couple of other interesting items. First, you can generate the C# or VB code for the current regular expression and copy it to the Clipboard:

Second, and more interesting, you can compile one or more regular expressions (including saved .regex projects) into a compiled assembly, which you can later reference from any .NET application. Using such compiled regexes is obviously faster than defining them in code, because you skip the parsing step:

 

That's it. You can use the YART tool for your own use, study its source code, modify and expands it as you like. If you find any major problems or add some noteworthy feature, just let me know.

C# | Regex | Tools | Visual Basic
8/29/2006 8:00:02 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [4]  | 
 Saturday, July 01, 2006

My new C# book has been released a few months ago, but I was so busy with my everyday activities that I didn't even blogged about it. Instead of going to the beach, today I sat down a prepare a home page for the book, from where you can download two sample chapters: Chapter 5 "Arrays and Collection" and Chapter 10, "Custom Attributes", as well as all the code samples and the errata doc.

Books | C#
7/1/2006 10:35:25 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 

At last I found the time to sort all the notes from readers, pointing at typos and mistakes in my VB2005 book, and to update the errata document that you can download from the book's home page. I have clearly marked the additions with a "New" marker. Most of these notes are typos that don't affect how code works, with the notable exception of the fix to the Evaluate function in Chapter 16 (regular expression).

My grat thanks to all the readers who took the time to write me to inform of the mistakes they found, with a special mention to Dan Karmann, who actually methodically proof-read the book and found about twenty of them.

 

7/1/2006 9:01:26 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [1]  | 
 Wednesday, May 31, 2006

Visual Basic 6's App object exposes a useful property named PrevInstance, which allows you to determine whether there are other instances of the same application running. This property has never been implemented in VB.NET, even though a VB2005 application can use the StartupNextInstance event for this purpose.

' to display this code, open the Application page of the My Project designer and click the Application Events button
Namespace
My
  
Partial Friend Class MyApplication
     
Private Sub MyApplication_StartupNextInstance(ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupNextInstanceEventArgs) Handles Me.StartupNextInstance
         ' another instance of this application has been launched
      End Sub
  
End Class
End
Namespace

The problem of this approach is that the event is raised in the first instance of the application, not in the new instance, therefore when the VB.NET application starts you can't determine for sure whether it is the only instance in the system. In other words, you can learn only whether and when another application is launched, not if the current applicatin is the first and only running instance.

.NET developers have solved this problem in a variety of ways, for example using Mutexes or by iterating over the collection returned by the Process.GetProcesses method. In .NET we have a new and more elegant alternative, based on the System.Threading.Semaphore type. A semaphore is a counter which can be incremented and decremented. If its value becomes zero, a thread can't decrement it and must wait for another thread to increment it to a value >0. Interestingly, if the semaphore has a name it becomes a system-wid object which can be shared by all the applications that ask for a reference to a semaphore with that specific name. It is therefore sufficient that the app creates a semaphore with a unique name just after its launch (e.g. it can be a name that includes the executable's path) to have all the instances of a given app share the same semaphore and therefore the same counter. The only problem left to solve is ensure that the semaphore's counter be correctly resotred when the application terminates, but this is easy to achieve by means of a Finalize method.

In addition to the PrevInstance property - which returns False if the application was the only running instance when the application was launched - the following VB6App class exposes also the InstanceCount property, which returns the total number of instances in that moment (the current app is included in the count). Here's the VB2005 version of this class:

Class VB6App
   ' the default instance
  
Private Shared DefValue As New VB6App

   ' the system-wide semaphore
  
Private semaphore As System.Threading.Semaphore
   ' initial count for the semaphore (very high value)
  
Private Const MAXCOUNT As Integer = 10000

   Private Sub New()
      Dim ownership As Boolean = False
      ' create a unique name, but strip invalid characters
     
Dim name As String = "VB6App_" & System.Reflection.Assembly.GetExecutingAssembly().Location.Replace(":", "").Replace("\", "")
      semaphore = New System.Threading.Semaphore(MAXCOUNT, MAXCOUNT, name, ownership)
      ' decrement its value 
      semaphore.WaitOne()
      ' if we got ownership, this app has no previous instances
      m_PrevInstance = Not ownership
   End Sub

   ' the PrevInstance property returns True if there was a previous instance running 
  
' when the default instance was created
  
Private Shared m_PrevInstance As Boolean

   Public Shared ReadOnly Property PrevInstance() As Boolean
      Get
         Return m_PrevInstance 
      End Get
   End Property

   ' return the total number of instances of the same application that are currently running 
  
Public Shared ReadOnly Property InstanceCount() As Integer
      Get
         ' release the semaphore and grab the previous count 
        
Dim prevCount As Integer = DefValue.semaphore.Release()
         ' acquire the semaphore again
        
DefValue.semaphore.WaitOne()
         ' eval the number of other instances that are currently running 
        
Return MAXCOUNT - prevCount
      End Get
   End Property

   Protected Overrides Sub Finalize()
      ' increment the semaphore when the application terminates
     
semaphore.Release()
   End Sub

End Class

Notice that this class has a Finalize method without implementing IDisposable. It is one of those special cases when it is OK to violate the Dispose-Finalize pattern.

To understand how the class works, just keep in mind that the Semaphore.Release method increments the internal counter, whereas the WaitOne method decrements it. You must test the VB6App.PrevInstance property as soon as the application starts, for example in the Sub Main method or in the Load event hander for the main form, as to let the VB6App class store the boolean value internally. The same form might then test the value of the InstanceCount property on exit, for example if you need to run some cleanup code when the last application instance is about to terminate:

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
   If Not VB6App.PrevInstance Then 
      ' open the common log file
      ' ...
   End
If
End Sub

Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
   If VB6App.InstanceCount = 1 Then
     
' close the common log file.
      ' ...
  
End If
End Sub

Here's the C# version of the class. (I fixed the original version to fix two syntax errors mentioned in one of the comments below.)

public class VB6App
{
   // the default instance 
   private static VB6App DefValue = new VB6App(); 
   // the system-wide semaphore
   private System.Threading.Semaphore semaphore; 
   // initial count for the semaphore (very high value)
   private const int MAXCOUNT = 10000;

   private VB6App() 
   { 
      // create a named (system-wide semaphore)
      bool ownership = false
      // create the semaphore or get a reference to an existing semaphore

      string name = "VB6App_" + System.Reflection.Assembly.GetExecutingAssembly().Location.Replace(":", "").Replace("\\", "");
      semaphore = new System.Threading.Semaphore( MAXCOUNT, MAXCOUNT, name, out ownership); 
      // decrement its value
     
semaphore.WaitOne(); 
      // if we got ownership, this app has no previous instances
      m_PrevInstance = !ownership;

   }

   // the PrevInstance property returns True if there was a previous instance running
  
// when the default instance was created

   private static bool m_PrevInstance ;

   public static bool PrevInstance 
  
      get 
     
         return m_PrevInstance ; 
     
   }

   // return the total number of instances of the same application that are currently running

   public static int InstanceCount 
  
      get 
     
         // release the semaphore and grab the previous count 
        
int prevCount = DefValue.semaphore.Release(); 
         // acquire the semaphore again
        
DefValue.semaphore.WaitOne(); 
         // eval the number of other instances that are currently running 
        
return MAXCOUNT - prevCount; 
     
   }

   ~VB6App() 
  
      // increment the semaphore when the application terminates
     
semaphore.Release(); 
   }
}

5/31/2006 10:10:12 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, May 29, 2006

I have been so busy in May that I couldn't update the blog, not even to mention that my latest Microsoft Press book had been released and is now available on all major US bookstores. With fewer than 600 pages, Programming Microsoft Visual C# 2005: The Base Class Library is surely the shortest book I wrote. Not only that: it's also the book that took me less time to wrote. In fact, this book is basically the translation to C# of the second half of my VB 2005 book, more precisely of the chapters that have to do more with the .NET Framework and less with the C# language itself. Here's the Table of Contents:

1. .NET Framework Basic Types
2. Object Lifetime
3. Interfaces
4. Generics
5. Arrays and Collections
6. Regular expressions
7. Files, directories, and streams
8. Assemblies and resources
9. Reflection
10. Custom attributes
11. Threads
12. Object serialization
13. PInvoke and COM interop

Even though the book isn't specifically on the C# language, it adequately covers most of the new features of C# 2.0, such as generics, iterators, and anonymous methods.

Why a book on the BCL? Well, in these years I realized that far too many developers focus solely on high-level features - such as Windows Forms, ADO.NET, and ASP.NET - and often fail to leverage the full potential from other portions of the .NET Framework. For example, I have seen many apps that use verbose and unefficient validation rules that might be replaced by a single regular expression. Or apps that could be written in a fraction of time (and lines of code) if the author had been conscious of the full potential of reflection and custom attributes. Not to mention the fact that new .NET 2.0 features, such as generics, could make things only worse.

In general I find that most books that are "translated" from a different programming language are disappointing, so you might wonder why this book should be different. First, when I signed the contract for my VB 2005 book I already knew that the book would have been translated to C#, thus I planned the book so that its structure wouldn't be too VB-centric. Secoond, in the last four years I have been using C# in virtually all my programming projects - in fact I have surely written more C# code than VB code. For this reason, you'll find that the C# code is carefully optimized to use all the usual C#-specific techniques, such as iterators and anonymous delegates.

Shortly I will prepare a home page for the book on this site, with a couple of sample chapters. In the meantime, you can read more about the book (and hopefully order it) on Amazon's home page.

5/29/2006 8:50:45 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [4]  | 
 Wednesday, May 03, 2006

Yesterday I got an email from reader Claudio Fontana, with the following, deceiptively simple request: how can you avoid flickering while updating many controls on a form? The problem is especially serious when you need to add thousands of items to a ListView or a TreeView.

In VB6 this problem can be solved quite simply by temporarily setting the Visible (or Enabled) property to False for all the controls about to be updated: the control isn't actually hidden, yet the result of the update operation appears istantaneously when the property is reset to True. Just as interesting: the update operation is carried out much faster if the control is invisible, often twice as faster. Alas, this trick doesn't work in .NET, because as soon as you set the Visible property to False the control is immediately hidden. It's necessary to find another solution.

A few Windows Forms controls - namely the ListBox, ComboBox, ListView, and TreeView controls - do expose the BeginUpdate and EndUpdate methods, which allow you to "freeze" the control while you add items to it. Not only do they solve the flickering problem, they also speed up the update operation, tipically by a factor of 2.5x. However, if your form contains many controls that do NOT expose these methods, you must devise something else, and this was exactly the problem that Claudio submitted, after he unsuccessfully googled around on the 'Net looking for a solution.

The problem is quite intriguing, thus I decided to spend some time on it, until I came to the following solution. The idea is simple, and can be split in the following steps: (1) take a snapshot of the current form's appearance, by making a pixel-by-pixel copy into a bitmap, (2) create a PictureBox control as large as the form, and load the bitmap into the PictureBox, (3) add the PictureBox to the form's Controls collection and bring the PictureBox in front of all other controls, (4) while the user looks at the "frozen" image of the form, update your controls, using the BeginUpdate/EndUpdate mthods if possible to speed up execution, (5) when the update operation is completed, remove the PictureBox from the Controls collection, so that the user can now see the real form.

You just need one dozen statements to implement this algorithim, but I prepared a class to make the code more reusable and to ensure that it releases all resources correctly:

Public Class FormFreezer
  
Implements IDisposable

   ' The form being frozen
   Dim Form As Form
   ' the auxiliary PictureBox that will cover the form
   Dim PictureBox As PictureBox
   ' the number of times the Freeze method has been called
   Dim FreezeCount As Integer = 0

   ' create an instance associated with a given form
   ' and optionally freeze the form right away
   Public Sub New(ByVal form As Form, Optional ByVal freezeIt As Boolean = False)
      Me.Form = form
      If freezeIt Then Me.Freeze()
   End Sub

   ' freeze the form 
   Public Sub Freeze()
      ' Remember we have frozen the form once more
      FreezeCount += 1
      ' Do nothing if it was already frozen
      If FreezeCount > 1 Then Exit Sub

      ' Create a PictureBox that resizes with its contents
      PictureBox = New PictureBox()
      PictureBox.SizeMode = PictureBoxSizeMode.AutoSize
      ' create a bitmap as large as the form's client area and with same color depth
      Dim frmGraphics As Graphics = Form.CreateGraphics()
      Dim rect As Rectangle = Form.ClientRectangle
      PictureBox.Image = New Bitmap(rect.Width, rect.Height, frmGraphics)
      frmGraphics.Dispose()

      ' copy the screen contents, from the form's client area to the hidden bitmap
      Dim picGraphics As Graphics = Graphics.FromImage(PictureBox.Image)
      picGraphics.CopyFromScreen(Form.PointToScreen(New Point(rect.Left, rect.Top)), New Point(0, 0), New Size(rect.Width, rect.Height))
      picGraphics.Dispose()

      ' Display the bitmap in the picture box, and show the picture box in front of all other controls
      Form.Controls.Add(PictureBox)
      PictureBox.BringToFront()
   End Sub

   ' unfreeze the form
   ' Note: calls to Freeze and Unfreeze must be balanced, unless force=true 
   Public Sub Unfreeze(Optional ByVal force As Boolean = False)
      ' exit if nothing to unfreeze
      If FreezeCount = 0 Then Exit Sub
      ' remember we've unfrozen the form, but exit if it is still frozen
      FreezeCount -= 1
      ' force the unfreeze if so required
      If force Then FreezeCount = 0
      If FreezeCount > 0 Then Exit Sub

      ' remove the picture box control and clean up
      Form.Controls.Remove(PictureBox)
      PictureBox.Dispose()
      PictureBox = Nothing
   End Sub

   ' return true if the form is currently frozen
   Public ReadOnly Property IsFrozen() As Boolean
      Get
         Return FreezeCount > 0
      End Get
   End Property

   ' ensure that resources are cleaned up correctly
   Public Overridable Sub Dispose() Implements IDisposable.Dispose
      Me.Unfreeze(True)
   End Sub
End
Class

This is the C# version, translated from VB by Claudio:

public class FormFreezer: IDisposable
{

   // The form being frozen

   Form form;

   // the auxiliary PictureBox that will cover the form

   PictureBox pictureBox;

   // the number of times the Freeze method has been called

   int FreezeCount = 0;

 

   // create an instance associated with a given form

   // and freeze the form in base of flag freezeIt

   public FormFreezer(Form form, bool freezeIt)
   {

      this.form = form;

      if (freezeIt) this.Freeze();

   }

 

   // freeze the form 

   public void Freeze()
   {

      // Remember we have frozen the form once more

      // Do nothing if it was already frozen

      if (++FreezeCount > 1) 
         return;

      // Create a PictureBox that resizes with its contents

      pictureBox = new PictureBox();

      pictureBox.SizeMode = PictureBoxSizeMode.AutoSize;

      

      // create a bitmap as large as the form's client area and with same color depth

      Graphics frmGraphics = form.CreateGraphics();

      Rectangle rect = form.ClientRectangle;

      pictureBox.Image = new Bitmap(rect.Width, rect.Height, frmGraphics);

      frmGraphics.Dispose();

 

      // copy the screen contents, from the form's client area to the hidden bitmap

      Graphics picGraphics = Graphics.FromImage(pictureBox.Image);

      picGraphics.CopyFromScreen(form.PointToScreen(new Point(rect.Left, rect.Top)), new Point(0, 0), new Size(rect.Width, rect.Height));

      picGraphics.Dispose();

 

      // Display the bitmap in the picture box, and show the picture box in front of all other controls

      form.Controls.Add(pictureBox);

      pictureBox.BringToFront();

   }

 

   // unfreeze the form

   // Note: calls to Freeze and Unfreeze must be balanced, unless force=true

   public void Unfreeze(bool force)
   {

      // exit if nothing to unfreeze

      if ( FreezeCount == 0 ) 
         return ;

      // remember we've unfrozen the form, but exit if it is still frozen

      FreezeCount -= 1;

      // force the unfreeze if so required

      if (force) 
         FreezeCount = 0;

      if (FreezeCount > 0) 
         return;

      // remove the picture box control and clean up

      pictureBox.Controls.Remove(pictureBox);

      pictureBox.Dispose();

      pictureBox = null;

   }

 

   // return true if the form is currently frozen

   public bool IsFrozen
   {

      get { return (FreezeCount > 0); }

   }

 

   void IDisposable.Dispose()

   {

      this.Unfreeze(true);

   }

}

Using the FormFreezer class is quite simple. Here's a code sample, which assumes that it is located inside a form class so that the Me keyword points to the current form:

   Dim ff As New FormFreezer(Me, True)
   ' update controls here
  
' ...
  
ff.Unfreeze()

The class implements IDisposable, thus you can bracket the update code in a Using block, either in C# or in VB2005, and avoid an explicit call to Unfreeze:

   Using New FormFreezer(Me, True)
      ' Update controls here
     
' ...
  
End Using

Notice that calls to Freeze and Unfreeze must be balanced. If you call Freeze twice you then need two calls to Unfreeze to actually restore the updated form. This behavior allows you to call Freeze and then invoke a method that calls Freeze again and still have the code work correctly (provided that all methods use the same instance of the FormFreeze class).

5/3/2006 12:16:30 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [1]  | 
 Monday, May 01, 2006

Many, if not most, Windows Forms samples you can find on the 'net include one or more calls to unmanaged code in Windows DLLs, often in the form of calls to the SendMessage API methods to fix some of the (very few) missing features of .NET controls. The problem is, such a call to unmanaged code creates a problem when the program runs as a ClickOnce application, because it requires higher CAS privileges.

Even though this problem doesn't have a generic solution, when you just need to send a message to the control you are inheriting from, you can avoid an explicit call to SendMessage by invoking the protected DefWndProc method instead. For example, let's say that you are writing an enhanced ComboBox that exposes the TopIndex property, which can set or return the index of the first visible item in the list area. These two operations can be implemented by sending the control the CB_SETTOPINDEX or CB_GETTOPINDEX message, respectively. Here's how you can use the DefWndProc method instead of SendMessage:

Public Class ComboBoxEx
  
Inherits System.Windows.Forms.ComboBox

   Public Property TopIndex() As Integer
      Get
         Const CB_GETTOPINDEX As Int32 = &H15B
         Dim m As New Message()
         m.HWnd = Me.Handle
         m.Msg = CB_GETTOPINDEX
         Me.DefWndProc(m)
         Return m.Result.ToInt32()
      End Get
      Set(ByVal value As Integer)
         Const CB_SETTOPINDEX As Int32 = &H15C
         Dim m As New Message()
         m.HWnd = Me.Handle
         m.Msg = CB_SETTOPINDEX
         m.WParam = New IntPtr(value)
         Me.DefWndProc(m)
      End Set
   End Property

End Class

By the way, such an enhanced ComboBox can be useful when migrating a VB6 app to VB.NET. In fact, the VB6 ComboBox and ListBox controls expopse the TopIndex property, whereas under .NET only the ListBox control exposes this property. If you have any VB6 code that takes advantage of the ComboBox's TopIndex, the simplest approach is replacing the standard ComboBox with a ComboBoxEx instance.

5/1/2006 9:48:39 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, April 29, 2006

MsgHookX is an ActiveX DLL that allows VB6 developers to perform safe and efficient intra-applicaton subclassing. (In this context, intra-application sublclassing means that you can intercept any message that Windows sends to a window or a control in the current application, as opposed to a window created by another application, a much more difficult task.) I wrote this DLL many years ago, when VB5 was released, and mentioned it in several books and magazine articles. I made the DLL available on the vb2themax site, but I omitted to upload the DLL in this new, .NET-only site.

Well, given the many mails I continue to receive from readers, it seems that VB6 is still alive and in good shape, and many VB6 developers continune to happily subclass their controls. For this reason I decided to make the DLL available again on this site. Here's a summary of what it does:

  • safe subclassing: can be used within the IDE and in break mode without any risk of system crashes.
  • it provides the BeforeMessage and AfterMessage events for easy event-driven programming model
  • it additionally can notify incoming messages through the IMsgHookEvents secondary interface, for better performance and easier debugging (in some cases, events are inhibited in the IDE)
  • highest flexibility: you can decide to call the original window procedure yourself from within the BeforeMessage event/method and/or cancel the default processing for the message. You can also browse and modify the value that will be returned to the operating system.
  • the DLL's type library includes the definition of over 300 symbolic constants that define the most common Windows messages, so you don't have to use the API Viewer to include them in your applications.

Have fun with it, but - please, please! - turn to VB.NET as soon as you can, if you haven't yet.

4/29/2006 9:09:29 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 

I am working at the migration of a large VB6 project and at one point I needed to drop a few methods in the VB6 app, in such a way that the methods would be "invisible" to VB.NET. This action was necessary because these methods performed similarly to a .NET native method. Obviously, I had the option to manually delete the methods after the migration, but if their number is high and if you need to run the wizard more than once on the same VB6 app (as it happens frequently, in the process of preparing the app for the migration), then deleing these portions of code each time becomes a nuisance.

Apparently, you can't instruct the migration wizard to ignore one or more pieces of code. Nevertheless, the solution is quite simple: you just need to bracket these pieces of code - entire methods or just individual statements - in a #If Win32 ...#End If block. The Win32 compilation constant - that is probably ignored by most VB developers today - was introduced when Visual Basic 4 was released, and was recognized also by VB5 and VB6 (but not by any version of VB.NET). Visual Basic 4 is the only version of this language that is available in 16-bit and 32-bit edition, and this compilation constant (together with Win16) allowed to define blocks of code that were compiled under only one of those versions, while allowing to mantain a single source code for both. Therefore, the following VB6 code:

#If WIN32 Then
   Private Sub Do Something(ByVal n As Integer)   
      ' ...
   End Sub
#End If

is correctly migrated to VB.NET by the migration wizard as follows:

#If WIN32 Then
   Private Sub DoSomething(ByVal n As Short)
      ' ...
   End Sub
#End If

but this piece of code will be skipped over by the VB.NET compiler because the Win32 constant isn't defined under VB.NET.

This "feature" can actually create a problem if you are migrating a VB6 app that has been evolved from an older application that was originally written in VB4. In this case, in fact, it is possible that its source code contains one or more #If Win32 blocks. In most cases you *want* to migrate this code, but these portions will be ignored after the migration to VB.NET. If this is the case, you should then locate all the the occurrences of #If Win32 statements in the code and delete them before the migration.

4/29/2006 8:18:48 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, March 06, 2006

I am reorganizing my MP3 collection and found that I needed to rename a large quantity of files. Of course, there are many free utilities that allow this operation - and that can use MP3 tags in the process - but I thought that I might write one myself. Thanks to regular expressions, the task shouldn't be that hard. In fact, in a few minutes I came up with the following console application. As you see, most of the code is used to extract and validate arguments on the command line:

Imports System.Text.RegularExpressions
Imports System.IO

Module Renx

  
Function Main(ByVal args() As String) As Integer
     
Console.WriteLine("RENX (C) Francesco Balena / Code Architects Srl")

      Dim recurse As Boolean = False
     
Dim renameMode As Boolean = False
     
Dim oldNamePattern As String = Nothing
     
Dim newNamePattern As String = Nothing

      ' analyze each argument
     
For Each arg As String In args
        
Select Case arg.ToLower()
           
Case "/s", "-s"
              
recurse = True
            
Case "/r", "-r"
              
renameMode = True
           
Case "/h", "-h"
              
Return ShowHelp(0)
           
Case Else
              
If oldNamePattern Is Nothing Then
                 
oldNamePattern = "^" & arg & "$"
              
ElseIf newNamePattern Is Nothing Then
                 
newNamePattern = arg
              
Else
                 
Return ShowHelp(1)
              
End If
        
End Select
     
Next

      ' check that we have both mandatory arguments
     
If oldNamePattern Is Nothing OrElse newNamePattern Is Nothing Then
        
Return ShowHelp(1)
     
End If
     
' create the regex and check that pattern syntax is ok
     
Dim reSearch As Regex
     
Try
        
reSearch = New Regex(oldNamePattern, RegexOptions.IgnoreCase)
        
' test the replace pattern as well
        
Dim tmp As String = reSearch.Replace("a dummy string", newNamePattern)
      
Catch ex As Exception
         Console.WriteLine(
"SYNTAX ERROR: {0}", ex.Message)
        
Return 3
     
End Try
     
Console.WriteLine()

      ' iterate over all files in current directory (and its subdirectories, if recurse mode)
     
Dim searchOpt As SearchOption = SearchOption.TopDirectoryOnly
     
If recurse Then searchOpt = SearchOption.AllDirectories

      Dim parsedFilesCount As Integer = 0
     
Dim renamedFilesCount As Integer = 0
     
Dim errorsCount As Integer = 0
     
For Each oldFile As String In Directory.GetFiles(Directory.GetCurrentDirectory(), "*.*", searchOpt)
         parsedFilesCount += 1
        
' the regex applies to name only
        
Dim oldName As String = Path.GetFileName(oldFile)
        
Dim ma As Match = reSearch.Match(oldName)
        
If ma.Success Then
           
' this is the new name
           
Dim newName As String = ma.Result(newNamePattern)
            Console.WriteLine(oldFile)
            Console.Write(
" => {0}", newName)
            renamedFilesCount += 1
           
' proceed with rename only if not in simulation mode
           
If renameMode Then
              
Try
                 
Dim dirName As String = Path.GetDirectoryName(oldFile)
                 
Dim newFile As String = Path.Combine(dirName, newName)
                  File.Move(oldFile, newFile)
              
Catch ex As Exception
                  Console.Write(
" -- ERROR: {0}", ex.Message)
                  errorsCount += 1
              
End Try
           
End If
           
Console.WriteLine()
        
End If
     
Next

      ' Display a report
     
If renameMode Then
        
Console.WriteLine("Summary: {0} parsed files, {1} renamed files, {2} errors", parsedFilesCount, renamedFilesCount, errorsCount)
     
Else
        
Console.WriteLine("Summary: {0} parsed files, {1} files affected", parsedFilesCount, renamedFilesCount)
         Console.WriteLine()
         Console.WriteLine(
"NOTE: Running in simulation mode. Specify the /R option to actually rename files.")
     
End If
     
' Return an error code
     
If errorsCount = 0 Then
        
Return 0
     
Else
        
Return 2
     
End If
  
End Function

   Function ShowHelp(ByVal exitCode As Integer) As Integer
     
Console.WriteLine()
      Console.WriteLine(
"Syntax: RENX <oldnamepattern> <newnamepattern> [/R] [/S] [/H]")
      Console.WriteLine(
" oldnamepattern : regex that selects the files to be renamed")
      Console.WriteLine(
" newnamepattern : regex that specifies how files must be renamed")
      Console.WriteLine(
" /R : rename files")
      Console.WriteLine(
" /S : iterate over subdirectories")
      Console.WriteLine(
" /H : display this help")
      Console.WriteLine(
"NOTE: By default the program runs in simulation mode, and just displays how files would be renamed.")
      Console.WriteLine(
" You must specify the /R option to actually rename the files.")
     
Return exitCode
  
End Function

End Module

At the very minimum, the RENX utility requires two arguments: a regex that specifies which files in the current directory (and its subdirectories, if you add the /S option) must be renamed, and a second regex that specifies how to rename the files that are matched by the first regex. The power of RENX is the fact that the first regex can (actually, must) specify one or more groups of characters, and these groups are then referenced in the second regex. For example, let's suppose that I have a folder with the following files:

        01 Speak to Me.mp3
        02 On the Run.mp3
        03 Time.mp3
        04 The Great Gig in the Sky.vb3
        05 Money.mp3
        06 Us and Them.mp3
        07 Any Colour You Like.vbr
        08 Brain Damage.mp3
        09 Eclipse.vb3

and that I want to rename them as follows:

        01 - Speak to Me - The Dark Side of the Moon.mp3
        02 - On the Run - The Dark Side of the Moon.mp3
        03 - Time - The Dark Side of the Moon.mp3
        04 - The Great Gig in the Sky - The Dark Side of the Moon.vbr
        05 - Money - The Dark Side of the Moon.mp3
        06 - Us and Them - The Dark Side of the Moon.mp3
        07 - Any Colour You Like - The Dark Side of the Moon.vb3
        08 - Brain Damage - The Dark Side of the Moon.mp3
        09 - Eclipse - The Dark Side of the Moon.vbr

Here's the RENX command that does it:

        RENX "(\d\d) (.+?)(\..+)"    "${1} - ${2} - The Dark Side of the Moon.${3}"

Notice that the first regex creates three groups by enclosing them in parenthesis: (\d\d) matches the song number, (.+?) matches the song title, and (\..+) matches the file extension, dot included. The second argument can then reorder these three groups, using the ${N}, where N is the position of the group as specified in the first regex. It is therefore to insert a dash after the song number, and the albumname after the song title.

Because the RENX utility is quite dangerous, by default it doe NOT rename the files, and it just lists how files would be renamed. To actually proceed with the rename operation, you must specify the /R option:

        RENX "(\d\d) (.+?)(\..+)"    "${1} - ${2} - The Dark Side of the Moon${3}" /R

That's all. You can play with the source code to extend the RENX utility as you prefer, and maybe turn it into a Windows Form application, or you can download the binary version from this link: Renx.zip (5.51 KB)

3/6/2006 5:30:10 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, February 28, 2006

A few minutes ago I made all the code samples for Programming Microsoft Visual Basic 2005: The Language available on the book home page. It's a 7.5M download that contains several thousand lines of carefully crafted, optimized, reusable source code, including the following:

  • a VB class that works with fractions
  • a base form for simplified data entry
  • a class to cache text files
  • several custom iterators for better For Each loops
  • tons of examples with generics
  • two expression evaluators, the former built on regexes, the latter implemented via on-the-fly compilation
  • a utility to display project statistics (a demo for regexes)
  • a RegexTester stand-alone utility, to help you build, test and compile regular expressions
  • an example of custom provider for My.Settings
  • an example of how you can intercept ANY event from ANY set of Windows Forms controls
  • attribute-based benchmarks
  • a complete infrastructure for writing Windows Forms plug-ins
  • an attribute-based library to write data-centric N-tier applications
  • several Visual Studio macros
  • Visual Studio visualizers for files, regex, and images
  • .... and a lot more

On the same page you can also find an errata document for typos and mistakes found after the book went to print.

Enjoy the code (and buy the book if you like it! :-) )

2/28/2006 7:10:13 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Thursday, February 23, 2006

The longer I work with generics, the more I like them, and I continue to discover new ways to simplify my code by using them. More specifically, I like the ability to write type-safe code that is also more concise, efficient, and (above all) readable, because I don't have to use tons of CType and DirectCast operators. Today I gathered the generic methods I use more frequently in the following module. They are tiny and simple, yet they save me a lot of time and code.

Module GenericFunctions

   ' Swap two variables
  
Public Sub Swap(Of T)(ByRef var1 As T, ByRef var2 As T)
     
Dim tmp As T = var1
      var1 = var2
      var2 = tmp
  
End Sub

   ' Type-safe version of the IIF function
  
' returns valueOnTrue if expression is True, else returns valueOnFalse
  
Public Function IIf(Of T)(ByVal expression As Boolean, ByVal valueOnTrue As T, ByVal valueOnFalse As T) As T
     
If expression Then
        
Return valueOnTrue
     
Else
        
Return valueOnFalse
     
End If
  
End Function

   ' Type-safe version of the Choose function
  
' returns the N-th element of a list of values, or the default value for T if index
  
' is less than 0 or higher than the number of values
  
Public Function Choose(Of T)(ByVal index As Integer, ByVal values() As T) As T
     
If index >= 0 AndAlso index < values.Length Then
        
Return values(index)
     
Else
        
Return Nothing
     
End If
  
End Function

   ' Return an array of the specified type
  
Public Function NewArray(Of T)(ByVal ParamArray values() As T) As T()
     
Return values
  
End Function

   ' Return the min value of a list
  
Public Function Min(Of T As IComparable)(ByVal firstValue As T, ByVal ParamArray values() As T) As T
     
Dim result As T = firstValue
     
For Each value As T In values
        
If result.CompareTo(value) > 0 Then result = value
     
Next
     
Return result
  
End Function

   ' Return the max value of a list
  
Public Function Max(Of T As IComparable)(ByVal firstValue As T, ByVal ParamArray values() As T) As T
     
Dim result As T = firstValue
     
For Each value As T In values
        
If result.CompareTo(value) < 0 Then result = value
     
Next
     
Return result
  
End Function

   ' Return True if a value is in specific range
  
Public Function InRange(Of T As IComparable)(ByVal testValue As T, ByVal minValue As T, ByVal maxValue As T) As Boolean
     
Return testValue.CompareTo(minValue) >= 0 AndAlso testValue.CompareTo(maxValue) <= 0
  
End Function

   ' Retrieve a dictionary element of a given type, or the provided default value if the element isn't found
  
' (two overloads)
  
Public Function GetDictionaryValue(Of TKey, TValue)(ByVal dict As Hashtable, ByVal key As TKey, ByVal defaultValue As TValue) As TValue
     
If dict.ContainsKey(key) Then
        
Return CType(dict(key), TValue)
     
Else
        
Return defaultValue
     
End If
  
End Function

   Public Function GetDictionaryValue(Of TKey, TValue)(ByVal dict As Dictionary(Of TKey, TValue), ByVal key As TKey, ByVal defaultValue As TValue) As TValue
     
' If the key is in the dictionary, the following statement stores the corresponding value
     
' in defaultValue, else it leave defaultValue unchanged
     
dict.TryGetValue(key, defaultValue)
     
Return defaultValue
  
End Function

End Module

Most methods are self-explanatory. One of the most useful ones is NewArray, which lets you create an array and pass it on-the-fly to a method. Let's say the the DoSomething method takes an array of Integers. These are the options you have in VB2005:

    ' 1. Create the array first, than pass it
    Dim values() As Integer = {1, 2, 3, 4, 5}
    DoSomething(values)

    ' 2. Create the array on the fly using the nearly-undocumented syntax
    DoSomething(New Integer() {1, 2, 3, 4, 5})

I often use the second syntax, but I noticed that relatively few developers know it. My code is much more readable with the NewArray method

    ' 3. Use the NewArray generic function to create the array on the fly
    DoSomething(NewArray(1, 2, 3, 4, 5))

The NewArray method proves to be quite useful also to build For loops whose index can take any sequence of values:

    ' Test whether "number" is a prime number in the range 1-1000
    For Each n As Integer In NewArray(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31)
       If (number Mod n) = 0 Then Console.Write("{0} is not prime", number): Exit For
    Next

2/23/2006 8:08:53 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
 Wednesday, February 15, 2006

In the VB6 world - and in the COM world, in general - you "destroy" an object by simply putting it to Nothing, unless of course there are other variables that are pointing to that specific instance. The VB6 runtime invokes the Class_Terminate method, if it exists, then it deallocates all the resources belonging to the object, memory included. This is a recursive process, and destroys any object that is owned by the object being set to Nothing.

We know well that setting a VB.NET object variable to Nothing doesn't fire any event, because the Terminate event isn't supported in .NET. If the object implements the Finalize method, this method is invoked by the garbage collection, but this invocation occurs some time later. The time interval between the Set to Nothing statement and the invocation of Finalize depends on many factors, but it could be as long as a few minutes or even hours, if the application doesn't allocate many objects and doesn't stress the garbate collector.

This behavioral difference can be a serious issue when migrating a VB6 application to VB.NET. For example, if the code in Class_Terminate closes a database connection, or a file, or a serial port, or deletes confidential information from disk, or unloads a form, then the delay between the "logical" destruction of the object (i.e. the Set to Nothing) and its "physical" destruction (when the Finalize method runs) can compromise the correct working of the application after its migration to the .NET world.

Unfortunately, it isn't possible to have VB.NET "automagically" behave like VB6 in this respect. However, it is possible to take a step that can greatly reduce the problem, without much impact on the code structure. Once again, this is possible thanks to generics. To see how, create a Module and add the following code to it, so that the SetNothing method is visible to the entire application:

Public Sub SetNothing(Of T)(ByRef obj As T)
  
' Dispose of the object if possible
  
If obj IsNot Nothing AndAlso TypeOf obj Is IDisposable Then
     
DirectCast(obj, IDisposable).Dispose()
  
End If
  
' Decrease the reference counter, if it's a COM object
  
If Marshal.IsComObject(obj) Then
     
Marshal.ReleaseComObject(obj)
  
End If
  
obj = Nothing
End Sub

Next, you can apply the search-and-replace feature in Visual Studio to the code produced by the migration wizard, to replace all occurrences of the var = Nothing pattern with the SetNothing.(var) pattern. Obviously, this technique doesn't really solve all the abovementioned problems, because it works only with the variables that are explicitly set to Nothing via code, and doesn't work with variables that are implicitly cleared when they exit the current scope (for example at the end of the method).

Notice that the search-and-replace operation include a variable part - that is, the name of the instance that is set to Nothing - therefore making this replacementement automatically seems impossible or unpractical. But, fortunately, Visual Studio supports regular expressions in search-and-replace operations (as I explain here), therefore you can search for the <{:i} = Nothing string and specify the SetNothing(\1) string in the Replace with field. Et voilà :-)

NOTE for those who are unfamiliar with regexes: the Find What string specifies that you want to search for a variable name (:i) that is located at the beginning of a word (<); curly braces in {:i} cause the variable name to be tagged, so that you can refer to the variable name in the Replace With regular expression, by means of the \1 placeholder. That's it.

2/15/2006 7:23:34 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, February 12, 2006

Great news for yours truly.

Practical Guidelines and Best Practices for Visual Basic and Visual C# Developers won an Excellence Award from the Society of Technical Communication, which selects the best books, sites, and coursewares published in the year. Not just programming-related or computer-related publishing, but technical publishing in the wider sense. As a matter of fact, among the books that won the award, only about ten have to do with software. And the book I co-authored with Giuseppe Dimauro (the other Italian RD) is the only book from Microsoft Press that won the award. I guess it's something we can be quite satisfied of :-)

It's an important acknowledgement and I am especially happy because it's the first book I wrote that isn't part of the "Programming Visual Basic" series, which now counts four editions (VB6, 2002, 2003, and 2005). The "Practical Guideline" book is a truly unique book, the first and only coherent collection of guidelines for .NET developers.

 

2/12/2006 8:51:08 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
 Sunday, January 29, 2006

In this period I am actively researching the many problems you face when migrating VB6 apps to .NET. I don't like *migrating* applications, because I always prefer to rewrite them from scratch to leverate all the features of .NET and above all because the conversion wizard doesn't do a great job and produces ugly and non-maintenable code. Better, the wizard does a decent job, as long as it doesn't have to handle incompatibilities between VB6 and VB.NET.

Some of these incompatiblities, however, can be solved with a bit of imagination, especially now that VB2005 has features that weren't available before. For example, consider the "classic" problem of converting arrays with a lower index other than zero, a problem that has bothered all VB6 developers trying to porting complex code to VB.NET. Let's say you have this code:

      Dim arr(1 to 10) as Integer
      Dim i As Integer, prod As Integer, v As Variant
      For i = LBound(arr) To UBound(arr)
         arr(i) = i
      Next
      For Each v in arr
         prod = prod * v
      Next

The conversion wizard will replace the index "1" with the index "0", therefore the array has one more element. It's evident that, at the end of execution, the value of prod will be zero, whereas it should be equal to the factorial of 10. This sort of bug is quite subtle, and in practice you're forced to scrutinize your source code and re-test the application entirely. A better, manual approach consist of fixing the Dim statement to "shift" the array so that its first non-empty element has zero index, and then modify ALL the references to the elements of the array, to account for the shift:

      Dim arr(0 to 10-1) as Integer     
      Dim i As Integer, prod As Integer
      For i = LBound(arr) To UBound(arr)
         arr(i - 1)  = i
      Next

Unfortunately, also this approach requires a lot of time and attention, and in some cases it can't be used, for example when the array is passed to a method that must work with arrays of any type (and whose code doesn't know that it has to shift the index). In yet other cases, the VB6 source code might use the value returned by LBound or UBound, and this code wouldn't work well after the migration.

The question is therefore: is it possible to convert this code to VB2005 without having to worry about all these issues? The solution has been relatively simple, thanks to generics and a few tricks with inheritance:

' Base class

Public Class VBArrayBase
  
Protected Friend lowerIndex As Integer
  
Protected Friend upperIndex As Integer
End Class

' One dimensional array of type T

Public Class VBArray(Of T)
  
Inherits VBArrayBase
  
Implements IEnumerable

   Dim items() As T

   Sub New(ByVal lowerIndex As Integer, ByVal upperIndex As Integer)
     
Me.lowerIndex = lowerIndex
     
Me.upperIndex = upperIndex
     
ReDim items(upperIndex - lowerIndex)
  
End Sub

   Default Property Item(ByVal index As Integer) As T
     
Get
        
Return items(index - lowerIndex)
     
End Get
     
Set(ByVal value As T)
         items(index - lowerIndex) = value
     
End Set
  
End Property

   Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
     
Return items.GetEnumerator()
  
End Function

End Class

Notice how simply the class provides support for For Each loops: it just has to return the IEnumerator object of the inner array. At this point I just needed to extend the LBound and UBound support to the new class. To do so, I created the following public module:

Public Module ArrayFunctionsVB6
   Function LBound(ByVal arr As Array, Optional ByVal rank As Integer = 1) As Integer
     
Return Microsoft.VisualBasic.Information.LBound(arr, rank)
  
End Function

   Function UBound(ByVal arr As Array, Optional ByVal rank As Integer = 1) As Integer
     
Return Microsoft.VisualBasic.Information.LBound(arr, rank)
  
End Function

   Function LBound(ByVal arr As VBArrayBase, Optional ByVal rank As Integer = 1) As Integer
     
If rank = 1 Then
        
Return arr.lowerIndex
     
Else
        
Throw New IndexOutOfRangeException()
     
End If
  
End Function

   Function UBound(ByVal arr As VBArrayBase, Optional ByVal rank As Integer = 1) As Integer
     
If rank = 1 Then
        
Return arr.upperIndex
     
Else
        
Throw New IndexOutOfRangeException()
     
End If
  
End Function
End
Module

The module must expose two overloads for each method, one overload for standard arrays and the other for the new VBArray(Of T) class. Alas, you can't have a project that references two distinct modules - one in the VB compatiblity library and one in another DLL - where each module contains a different overload of the same method. In this case, only one of the two methods is visible to the main program.

Another interesting detail: the code inside the LBound and UBound methods needs to access the Friend members of the VBArray(Of T) class, but these methods can't have VBArray(of T) in the parameter list, because they aren't generic methods. This is the reason why I have the VBArray(Of T) class derive from VBArrayBase, where these Friend members are defined.

Thanks to the VBArray(Of T) class and the ArrayFunctionsVB6 module, you can migrate the VB6 code by changing only the DIM statement, as follows:

     Dim arr As New VBArray(Of Short)(1, 10)      ' Short instead of Integer

The remainder of the code will work flawlessly, exactly as in VB6, including the For Each loop and calls to LBound and UBound. Seeing is believing! :-)

1/29/2006 3:30:03 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, January 16, 2006

If I could get istantaneous results for the following simple two-question survey

  1. Is Visual Studio the application that you use most often?
  2. Did you ever use regex searches in the VS Find dialog box?

I'd bet that 80% of you would answer YES to the first question, but 99% of you would answer NO to the second question, which would be a rather weird result. Regex searches are among the most powerful VS features, yet few developers use them or even know that they exist.

IMHO, the real problem is that VS regex's syntax is completely different from the syntax you use with the Regex class, therefore using this feature requires that you learn yet another regex dialect. This is a bit too much for most developers. Microsoft should allow the standard regex syntax in this dialog: they could implement this change very easily and in a short time, without caring about backward compabibility issues.

While waiting for Microsoft to offer this little-big innovation, you can have fun with what you have today. Here are a few examples, excerpted from my new book Programming Microsoft Visual Basic 2005: The Language:

:i = :z   Search assignments of an integer constant (:z) to a variable (:i). In VB, but more rarely in C#, it can deliver false matches, when the = operator is used in an expression.

:i = :q   Search assignments of a quoted string constant (:q) to a variable.

(Dim|Private|Public) :i As String   Search for variable declarations of string type (VB only). You can easy adapt it to other data types.

Dim <(:Lu(:Ll)*)+> As   Search for local VB variables that use a PascalCase naming convention and therefore violate Microsoft's guidelines. (Local variables should use the camelCase convention.)

^:b*'.+\n   Search for comment lines in VB, that is, lines that begin with an apostrophe. (It doesn't consider the REM keyword.) You can replace the apostrophe with // to use this search pattern in C# as well.

Dim {:i} As (.|\n)#<\1>    Highlights the portion of code between the declaration of a local variable and the first occurrence of that variable in the method. You can repeat this search for all the local variables in a method and check whether you should refactor your code by moving the declaration closer to where the variable is used for the fist time. (See effect in figure below.)

1/16/2006 5:13:02 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, January 07, 2006
In a week or two my new book, Programming Microsoft Visual Basic 2005: The Language will be available on Amazon and other online stores. Towards the end of January it should be available in most bookstores in the US.

In the meantime, you can get a taste of how the book looks like by reading two online chapters: Chapter 11, "Generics", and Chapter 18, "Reflection".

1/7/2006 4:53:16 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, December 28, 2005

Today I spent a few minutes trying to understand the behavior of nullable types in C#, in particular how comparison operators evaluate their result. An asimmetry exists between the == and != operators and all the other comparison operators. In fact, the equal and not-equal operators work perfectly even when the two operands are both null, as in the following code:

int? x1 = null;
int? x2 = null;
Console.WriteLine(x1 == x2);    // => True
Console.WriteLine(x1 != x2);     // => False

The remaining comparison operators, however, don't deal with null values in the same correct way. If either operand is null, these operators always return false. This inconsistent behavior brings to weird situations, in which two values can satisfy the "equal to" relation but not the "equal to or greater than" relation:

int? x1 = null;
int? x2 = null;
Console.WriteLine(x1 == x2);    // => True
Console.WriteLine(x1 >= x2);    // => False

To compare nullable values in a coherent way, you should use the Nullable.Compare static method, which considers null as less than any other value:

switch (Nullable.Compare(d1, d2))
{
  
case -1:
     
Console.WriteLine("d1 is null or is less than d2");
     
break;
  
case 1:
     
Console.WriteLine("d2 is null or is less than d1");
     
break;
  
case 0:
     
Console.WriteLine("d1 and d2 have same value or are both null.");
     
break;
}

In some cases, you can compare two nullable values by using the lowest value in the range in lieu of the "unknown" state, as in:

Console.WriteLine(x1.GetValueOrDefault(int.MinValue) >= x2.GetValueOrDefault(int.MinValue));

(Of course, you can use this approach only if you're sure that operands can be assigned the int.MinValue value.) Besides being available in VB2005 as well, a minor advantage of these techniques is that they generate fewer IL code that is also slightly faster than the code produced by comparison operators. These operators, in fact, always invoke the GetValueOrDefault AND the HasValue property behind the scenes.

C#
12/28/2005 6:46:25 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [2]  | 
 Friday, December 16, 2005

As I anticipated yesterday, here is the TOC of my newest Programming Microsoft Visual Basic 2005, to be available in stores in early January.

Part I : The Basics
 1. Introducing the .NET Framework
(12 pages): a quick overview of basic concepts in .NET programming.
 2. Basic Language Concepts (70 pages): modules, classes, variables, arrays, operators, etc. plus what you need to know about inheritance and attributes so that you can read next chapters, before chapter 8 and 19.
 3. Control Flow and Error Handling (40 pages): If, Select, For, For Each and other basic statements; error handling, with many not-so-obvious techniques to improve code efficiency and programming style.

 4. Using Visual Studio 2005 (56 pages): the many new features of VS2005 IDE, plus many old features that not all developers know; how to write a code snippet for VS2005; templates, refactoring, and a brief but intense tutorial on VS macros.

 5. Debugging and Testing (56 pages): breakpoints and tracepoints, data tips, how to write a cusom visualizers, trace commands and trace listeners (including custom listeners), benchmarks and profiling, unit testing, and code coverage.

Part II : Object-Oriented Programming
 6.
Class Fundamentals (42 pages): the "usual" story about classes, methods, properties, etc. plus the new partial classes and operator overloading, all peppered with the description of relatively unknown programming techniques.
 7. Delegates and Events (26 pages): a small chapters with many details and secrets on how to use these VB features in real-world apps. It includes the new custom events.
 8. Inheritance (34 pages): inheritance at its best, including visual (form) inheritance and many real examples.

 9. Object Lifetime (28 pages): everything you might need to know about garbage collection, the Dispose/Finalize pattern, weak references, GC generations, object resurrection, and other advanced techniques that can take your app to the next level
10. Interfaces (28 pages): how to define a custom interface and, above all, how to leverage those that .NET provides you with, such as IComparer and IEnumerable.
11. Generics (40 pages): half of what you want to know about this new great .NET 2.0 feature (the second half is in chapter 13), including constraints, nullable types, and many examples of programming techniques that are based on generics.

Part III : Working with the .NET Framework
12. .
NET Basic Types (50 pages): working with strings, numbers, and dates at their best, including many little/big new features of .NET 2.0
13. Arrays and Collections (53 pages): arrays, jagged arrays, "traditional" and generics collections, plus many tricks for writing less code that runs faster.
14. Regular Expressions (40 pages): a reference of regex syntax, plus many practical examples on data validation, data parsing, and even code parsing. If you aren't familiar with regexs you are missing a great occasion for writing better code in less time.
15. Files and Streams (42 pages): an overview of all the types in System.IO and the many new features in .NET 2.0, including ACL support, compressed streams, and the TextFieldParser type.
16. The My Namespace (48 pages): how to use the My namespace and how to extend it as you need.
17. Assemblies and Resources (44 pages): despite of their importance, resources (either simple or localized) are used rarely and unproperly by most developers; this chapter includes a complete description of the many important features added to NGEN.

Parti IV : Advanced Topics
18. Reflection
(58 pages): there is a lot to say about reflection; among the many examples I wrote an app that generates code on the fly, a scheduler for undoable actions, and a universal comparer class.
19. Custom Attributes (46 pages): this chapter includes a few complete and nontrivial examples of how a custom attribute can make your coding simpler, for example by means of Windows Forms plugins and a framework for n-tier apps.

20. Threads (54 pages): the Thread object, asynchronous delegates, thread pool, the SyncLock statement, all the synchronization types, including the new Semaphore. Plus a section on threading in Windows Forms aoos.
21. Object Serialization (32 pages): binary and SOAP serialization, version-tolerant serialization in .NET 2.0, the new attributes for serializatoin, custom serialization, serialization surrogates, the IObjectReference interface, and more.
22 PInvoke and COM Interop (40 pages): How to interact with unmanaged code: calling "classic" DLL and Windows API methods; using COM components (including the new registration-free components); writing .NET components that can be used from COM apps.

As I already explained, this book isn't a mere VB 2005 reference. Better, in addition to being a complete reference book, it is a digest of the many programming techniques that you can implement by leveraging the features of the language and the .NET Framework 2.0, including generics, threads, reflection, custom attributes, serialization, delegate, regular expressions, and more. All descriptions aim to writing faster and more robust code. I looked hard for a similar book on the market before writing this one. I believe I finally wrote a book that does VB justice.

It has been a real tour de force, which kept me busy from May, summer included. Today I have completed the very last edit to PDFs, then everything goes to the printer!

12/16/2005 2:32:12 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Thursday, December 15, 2005

Many readers are sending emails asking whether I was writing the 2005 edition of Programming Microsoft Visual Basic .NET. The answer is "yes and no".

Yes, I am writing the new edition of this book, updated to Visual Basic 2005.

No, I am not really writing a new edition of that book. The book I am finalizing this week is actually a brand new book, titled Programming Microsoft Visual Basic 2005: The Language. I decided to keep the title similar enough to the original one, to emphasize that the author is the same and that the approach is similar; however, I decided to added "The Language" postfix, to ring a bell in the mind of potential buyers who might otherwise believe that this is "just" an update to VB2005 and .NET 2.0.

The new book focuses only on the VB language and the .NET Base Class Library (BCL). It covers both old and new keywords as well as topics such as .NET data types, arrays and collections, streams, reflection, serialization, threading, PInvoke and COM interop. It does not cover high-level stuff such as Windows Forms, ADO.NET, and ASP.NET, though.

I thought hard about the Table of Contents of this new book, literally for months. In the previous edition I managed to squeeze in "only" 1400 pages virtually anything you need to work with .NET Framework 1.1, including advanced topics such as serviced components and remoting, Windows Forms and Web Forms custom control creation, security, and so forth. However, .NET 2.0 is much more complex that 1.1, and I estimated that I would have either needed to split the book in two volumes or be less specific on most topics. Both choices were unsatisfactory to me.

Also, I couldn't help noticing that there are too many great books around about Windows Forms or ASP.NET programming, and a single core-reference book is bound to be less complete than those books with a narrower focus. Granted, a book from a single author and that covers all these topics can offer a unified view of what is important in .NET programming, but I am sure that developers who are deeply interested in a specific area will buy a book that specializes in that area.

While I was taking note of what else could be found on bookstore shelves, I found out that all these high-level books often overlook the basics, for example out to get the best out of the language or basic data types such as DateTime, arrays, and collections. Most .NET developers know how to write great Windows Forms or ASP.NET applications, yet they don't know how to optimize string-intensive programs effectively. And I am not talking about the usual String vs. StringBuilder example, I am thinking of techniques such as this, this, or this. Another example is memory optimization: you can speed up your code by an order of magnitude using caching techniques based on the WeakReference type, or by means of a correct Dispose-Finalize pattern. Not to mention advanced techniques based on delegates and reflection, such as this, this,or this.

In the end, I realized that I could write a very good book on just the Visual Basic language and the most important facets of the BCL. Rather than (or in addition to) being a plain reference for VB keywords and .NET types, this book is more similar to a complete compendium of programming techniques that you can implement with these features. For example, there is one entire chapter devoted to custom attributes, with a few advanced examples of how they can help you in the design of your n-tier apps. In other words, instead of just listing what are your tools this book will explain how to leverage them using intermediate-to-advanced techniques, including nonobvious techniques based on generics, on-the-fly compilations, advantage use of delegates and custom events, and so forth.

Another important topic that Programming Visual Basic 2005: The Language book covers is Visual Studio and how to take advantage of its many editing and testing features. The book includes two long chapters (112 pages in total) which covers basic and advanced topics, including macro creation, unit testing (with Team System), debugging techniques, and more. I have never found a language book that focuses on productivity inside the IDE and I hope this new book fills this gap.

The book consists of 22 chapters, for more than 1000 pages. I'll publish its Table of Contents in another post very soon, hopefully tomorrow.

12/15/2005 1:56:08 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, December 14, 2005

In a previous post I introduced a tiny utility to clean up Visual Studio projects. I have introduced minor fixes to it since then, so this is the most recent version, which also deletes *.suo and *.user files and produces a report of all the files and folders that couldn't be deleted for any reason:

Imports System.IO

Module Module1
  
Sub Main(ByVal args() As String)
      Console.WriteLine(
"VSProjCleaner tool (C) 2005 Francesco Balena, Code Archirects")
     
If args.Length = 0 Then
        
Console.WriteLine("Removes BIN and OBJ folders, .suo and .user files from Visual Studio projects")
         Console.WriteLine()
         Console.WriteLine(
" SYNTAX: VSProjCleaner dirname")
         Console.WriteLine()
        
Return
     
End If
     
Console.WriteLine()

      ' Use current directory if no argument has been specified
     
Dim rootDir As String = Directory.GetCurrentDirectory()
     
If args.Length > 0 Then rootDir = args(0)
     
' Read all the folder names in the specified directory tree
     
Dim dirNames() As String = Directory.GetDirectories(rootDir, "*.*", SearchOption.AllDirectories)
     
Dim errorsList As New List(Of String)

      ' delete any .suo and vbproj.user file
     
For Each dir As String In dirNames
        
Dim files As New List(Of String)
         files.AddRange(Directory.GetFiles(dir,
"*.suo"))
         files.AddRange(Directory.GetFiles(dir,
"*.user"))
        
For Each fileName As String In files
           
Try
              
Console.Write("Deleting {0} ...", fileName)
               File.Delete(fileName)
               Console.WriteLine(
"DONE")
            Catch ex As Exception
               Console.WriteLine()
               Console.WriteLine(
" ERROR: {0}", ex.Message)
               errorsList.Add(fileName &
": " & ex.Message)
            
End Try
         
Next
      
Next

      ' Delete all the BIN and OBJ subdirectories
      
For Each dir As String In dirNames
         
Dim dirName As String = Path.GetFileName(dir).ToLower()
         
If dirName = "bin" OrElse dirName = "obj" Then
            
Try
               
Console.Write("Deleting {0} ...", dir)
               Directory.Delete(dir,
True)
               Console.WriteLine(
"DONE")
            
Catch ex As Exception
               Console.WriteLine()
               Console.WriteLine(
" ERROR: {0}", ex.Message)
               errorsList.Add(dir &
": " & ex.Message)
            
End Try
         
End If
      
Next
      
Console.WriteLine()
      Console.WriteLine(
New String("-"c, 60))
      
If errorsList.Count = 0 Then
         
Console.WriteLine("All directories and files were removed successfully")
      
Else
         
Console.WriteLine("{0} directories or directories couldn't be removed", errorsList.Count)
         Console.WriteLine(
New String("-"c, 60))
         
For Each msg As String In errorsList
            Console.WriteLine(msg)
         
Next
      
End If
   
End Sub
End
Module

You can download the binary version here: VSProjCleaner.exe (24 KB) 
or the complete solution here: VSProjCleaner_Source.zip (15.08 KB)

Using this tool couldn't be simpler: just run it from the command window and pass a folder name as an argument, enclosing it in double quotes if it contains spaces:
                            VSPROJCLEARNER "c:\my projects\testproj"
The tool recursively visits all the folders and deletes all BIN and OBJ directories, and deletes *.suo and *.user files. At the end of the process it lists all the folders and files that couldn't be removed, if any.

DISCLAIMER: I have tested this code against my own projects and it always worked correctly, but I don't should be held responsible for any loss of important files (for example, a .user file that has nothing to do with a Visual Studio project.). For this reason, I urge you to apply this tool only to a COPY of your projects

12/14/2005 9:58:58 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
 Thursday, December 08, 2005

Last spring I co-authored this book, Practical Guidelines and Best Practices for Microsoft Visual Basic .NET and Visual C# Developers, arguably the longest title in Microsoft Press's history. The book is a reasoned list of guidelines that all .NET developers should follow, actually is by far the largest collection of its kind you can find anywhere. It covers language syntax, memory usage, Windows Forms and ASP.NET applications, security, and more.

Unlike most other similar collections, though, we clearly divide the "rules" in guidelines (naming guidelines, comment usage, etc.) and best practices. The difference is subtle but important: most guidelines are primarily a style matter, whereas best practices impact the scalability, the speed, or the robustness of your application. This means that our guidelines are arbitrary and in fact we often offer alternate rules and clearly explain the pros and cons of each style.

You can learn more about the principles we used in the book's Introduction and in John Robbins's Foreword. (Unlike most foreword writers, John actually read each and every page in the manuscript and gave us some great advice about improving it.) Or click the figure to jump to the book's home page, where you can read three sample chapters and download the book's source code.

Today I have uploaded a 30-page Word document that contains a summary of all the rules covered in the book, orderly grouped by topic and with a reference where in the book each rule is explained. You can edit this document as you see fit, delete or edit the guidelines you aren't interested in, and so forth. We routinely use this document in internal code reviews or when we consult at customers' places, so we hope it will be useful to you as well.

P.S. You must register to access this material. We swear we'll never send you anything that vaguely resemble spamming, just 100% technical contents!

12/8/2005 2:50:28 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, December 03, 2005

Consider the following code, that converts all the elements of an Int32 array into the corresponding hex value:

' VB
Dim intArray() As Integer = {4, 6, 9, 10, 99, 233, 34, 88, 189}
Dim hexArray(intArray.Length - 1) As String
For i As Integer = 0 To intArray.Length - 1
   hexArray(i) = intArray(i).ToString(
"X")
Next

// C#
int[] intArray = {4, 6, 9, 10, 99, 233, 34, 88, 189};
string[] hexArray = new string[intArray.Length];
for ( int i = 0; i < intArray.Length; i++)
{
   hexArray[i] = intArray[i].ToString(
"X");
}

The question is: how can you make this code more concise in .NET 2.0? The first answer that might come up is to use the Array.ConvertAll method together with a C#'s anonymous method:

string[] hexArray = Array.ConvertAll<int,string>(intArray, new Converter<int,string>(
  
delegate(int n) { return n.ToString("X");}));

Actually, you can write even more concise code if you remember than the Microsoft.VisualBasic library already contains the Hex method, which matches the signature of the Converter<int,string> delegate. Using this method and delegate inference, you can shrink the code to:

' VB
Dim hexArray() As String = Array.ConvertAll(Of Integer, String)(intArray, AddressOf Hex)
// C#
string[] hexArray = Array.ConvertAll<int,string>(intArray, Microsoft.VisualBasic.Conversion.Hex );

I am certain that few C# developers will use this trick, but I thought it was worth mentioning. (Of course, you must add a reference to the Microsoft.VisualBasic.dll assembly if you work with C#.) The key idea, however, is that in some cases you don't need to write an anonymous method to accomplish a given task, because often you can find what you're looking for in the .NET Framework. For example, you can display all the elements of an array in the Console window with just one statement:

' VB
Array.ForEach(hexArray, AddressOf Console.WriteLine)
// C#
Array.ForEach(hexArray, Console.WriteLine);

There are many other methods in the VB library that you can use to convert all the elements of an array or a generic List, including UCase, LCase, LTrim, RTrim, and Trim.

12/3/2005 2:17:40 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Thursday, December 01, 2005

One of the .NET Framework features that fascinate me most is regular expressions, which I often use to simplify and speed up my applications. Well, at least this is what I believed until some time ago, when I was busy writing the forthcoming Programming Microsoft Visual Basic 2005: The Language (due in mid-January). This book is a core reference on the VB language and includes a section on the LIKE operator, which in recent years a overlooked in favor of regexes. I (mistakenly) assumed that the Like operator internally used the Regex classes, therefore surely it would have been slower. After all these years, I should have learned that I should never jump to conclusions without testing and benchmarking my code accurately. .

Let's say that you must check that a string has 9 characters, the first of which must be an uppercase "A" and the last four chars must be digits. This is how you'd perform this test with a regex:

Dim re As New Regex("^A....\d\d\d\d$")

and here's the version that uses the Like operator:

If teststring Like "A????####" Then Match = True

Surprise! Putting this code in a loop (but leaving the creation of the regex out of the loop) and using a string that makes the test succeed (e.g. "ABCDE1234"), the Like operator is about 4 times faster than the regular expression. Not bad, uh? But the biggest surprise came when I benchmarked the same test based on methods of the System.Char class exclusively:

If teststring.Length = 9 AndAlso teststring.Char(0) = "A"c AndAlso Char.IsDigit(teststring.Char(5)) Then
   AndAlso
Char.IsDigit(teststring.Char(6)) AndAlso Char.IsDigit(teststring.Char(7))
  
AndAlso Char.IsDigit(teststring.Char(8)) Then match = True

Despite of its length, this last test is about five times faster than the Lik operator, and therefore about 20 times faster than the regexes! The gap gets closer if using compiled regexes, but the System.Chars approach is by far the fastest of the lot.

The bottom line: (1) if you write VB code, use the Like operator instead of regexes if the condition isn't too complex, and (2) regardless of the language you work with, if you really want the highest performance, use the methods of the String and Char types, if the search operation isn't too complex.

12/1/2005 10:56:56 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Friday, November 25, 2005

Every now and then I get an email from a reader or a customer, who asks for clarifications on object finalization and disposing. As far as I know, the best article on this topic is this essay by Joe Duffy. It's over 25-page long, covers both .NET 1.1 and 2.0, and includes comments from gurus such as Jeffrey Richter e Chris Brumme. This is easily the definitive article on this topic and I urge you to read it if you haven't already.

The Dispose-Finalize pattern is objectively a complex matter. However, in most cases it can be simplified significantly if you use the following approach: (1) the class with the Dispose/Finalize method should wrap only one single unmanaged resource, and (2) this finalizable class should be private and nested inside another disposable (but not finalizable) type. The outer class is the only class that can use the finalizable class.

This simple trick enables the GC to immediately release all the memory used by the wrapper (disposable) class even in the worst case - that is, if the client code omits to invoke the Dispose method - and simplifies the structure of the type that uses the unmanaged resource. A listing is worth one thousand words, thus here is the C# version of what I mean:

// the class that clients use to work with the unmanaged resource
class WinResource : IDisposable
{
  // private field that creates a wrapper for the unmanaged resource
  private UnmanagedResourceWrapper wrapper = null;
  // this is true if the object has been disposed of
  bool disposed = false;

  public WinResource(string someData)
  {
    // allocate the unmanaged resource here
    wrapper = new UnmanagedResourceWrapper(someData);
  }

  // a public method that clients call to work with the unmanaged resource
  public void DoSomething()
  {
    // throw if the object has been already disposed of
    if ( disposed )
      throw new ObjectDisposedException("");

    // this code can pass the wrapper.Handle value to API calls.
    // ...
  }

  public void Dispose()
  {
    // avoid issues when multiple threads call Dispose at the same time.
    lock ( this )
    {
      // do nothing if already disposed of
      if ( disposed )
        return;
      // dispose of all the disposable objects used by this instance
      // including the one that wraps the unmanaged resource
      // ...
      wrapper.Dispose();
      // remember this object has been disposed of
      disposed = true;
    }
  }
 
  // the nested private class that allocates and release the unmanaged resource
  private sealed class UnmanagedResourceWrapper : IDisposable
  {
    // an invalid handle value, that the wrapper class can use to check
    // whether the handle is valid
    public static readonly IntPtr InvalidHandle = new IntPtr(-1);

    // a public field, but accessible only from inside the WinResource class 
    public IntPtr Handle = InvalidHandle;

    // the constructor takes some data and allocates the unmanaged resource (eg a file)
    public UnmanagedResourceWrapper(string someData)
    {
      // this is just a demo...
      this.Handle = new IntPtr(12345);
    }

    // the Dispose method can be invoked only by WinResource class
    public void Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    // the finalizer
    ~UnmanagedResourceWrapper()
    {
      Dispose(false);
    }

    // This is where the unmanaged resource is actually disposed of.
    // Notice that it takes an argument only for compliance with .NET coding standards
    // but the disposing argument is never used, because in all cases this class
    // can access and release only the single unmanaged resource it wraps.
    private void Dispose(bool disposing)
    {
      // exit now if this object didn't completed its constructor correctly
      if ( this.Handle == InvalidHandle )
        return;
  
      // release the unmanaged resource 
      // eg. CloseHandle(Handle);
      
      // finally, invalidate the handle
      this.Handle = InvalidHandle;
    }
  }
}

Notice that, if the unmanaged resource must interact with other fields, this interaction should be taken care of inside the WinResource class, not in the nested class. The UnmanagedResourceWrapper works only as a wrapper for the handle and shouldn't contain other fields or methods, besides those shown in the above listing. The code in the WinResource class must coordinate all the resources being used, both managed and unmanaged ones, and must release all of them in its Dispose method. But if the client code omits to call the Dispose method, the destructor in the nested class will orderly release the unmanaged resource during the next garbage collection.

Let's see all the advantages of this simplified approach.

  • The requirement that you shouldn't access reference fields from inside the Finalize method is automatically satisfied, because the only field of the UnmanagedResourceWrapper type is a handle (a value type).
  • If the client code omits to invoke the WinResource.Dispose method before the WinResource object goes out of scope, the WinResource object is removed from the heap anyway at the first GC; only the few bytes used by the UnmanagedResourceWrapper object survive in the heap and will be promoted to generation 1 or 2. Therefore this technique is more efficient than writing a single finalizable object that allocates both managed and unmanaged resource.
  • The UnmanagedResourceWrapper class is private and you can't inherit from it, therefore you can mark it as sealed. This means that you never have to worry about the Dispose/Finalize pattern in derived classes - a topic on which tons of digital ink has been spilled. It is possible to inherit from WinResource as you'd do with disposable class, therefore there are no limitations in this respect. (It's exactly like when you inherit from other disposable classes such as FileStream.)
  • The UnmanagedResourceWrapper is private and nested in another type and it isn't possible to achieve a refernence to one of its instances; therefore, a client can't "resurrect" a UnmanagedResourceWrapper object during the finalization step, a technique that is rarely useful and often dangerous. (Even though I show in my Programming Visual Basic .NET book how you can use it to implement an object pool.)
  • The UnmanagedResourceWrapper constructor performs a single "atomic" action; if this action fails, the value of the handle is still qual to InvalidHandle, therefore the code in the Finalize method can detect this special value and do nothing in that case. There are only two cases: either the unmanaged resource has been correctly allocated or an exception prevented it from being created, and you don't have to worry about an object that has been built only partially because of an exception in its constructor.
  • Many other recommendations related to the Dispose/Finalize pattern become void, such as the one that dictates that you should neither write finalizers inside structures nor calling virtual methods from inside the finalizer. In fact, the UnmanagedResourceWrapper class is sealed and has no virtual methods. Nor do you have to worry about versioning issues.
  • Another advantage: the UnmanagedResourceWrapper class is so simple and generic that you can ofter reuse it as-is (or with minor edits) inside other classes, by means of a plain copy-and-paste action. Being a nested class, you don't even need to change its name to avoid name collisions.

I am sure that in some cases this simplified pattern can't be used, though it always worked well in my applications. I believe that it's quite odd that this simplified approach is rarely mentioned in articles and books on this topic.


Technical matters aside, I think that another kind of consideration about the Dispose/Finalize pattern is in order.

In my opinion, it is essential to put a lot of emphasis on the fact that the Dispose/Finalize pattern should be used only when your type invokes unmanaged code that allocates unmanaged resources (including unmanaged memory) and returns an handle that you must use eventually to release the resource. If the unmanaged resource is already wrapped by a .NET object (e.g. a FileStream or a SqlConnection) or a COM object, the .NET class that uses the resource must implement IDisposable, not the finalizer. And you must implement the Finalize method only if your code assigns the handle to a class-level field. If the handle is assigned to a local variable and the unmanaged resource is released before exiting the method - possibly in the Finally section of a Try block - you don't even have to implement the IDisposable interface. One of the few exceptions to this rule is when your managed code allocates unmanaged memory directly, by means of the System.Runtime.InteropServices.Marshal type.

I talked to many developers who believe that, in doubt, they should always implement the Finalize method, just in case. This is a common mistake. Defining a finalizable class without a real reason to do so can hurt performance, because the CLR takes slightly longer to allocate finalizable objects, because it has to register them in the f-reachable queue. And in the worst case - that is if the caller omits to call to the Dispose method - a finalizable object can have even more impact on performance, because it will be promoted to a higher generation without any real reason for such an overhead.

To recap: when do you really need to implement the Finalize method? Thinking of all the commercial apps I worked on in these years, I'd say that I used this pattern no more than 4 or 5 times. For sure, I used it more frequently in books and articles than in the real world. :-)

11/25/2005 10:37:38 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, November 22, 2005
I work with Microsoft Press since 1998 and I wrote as many as 5 books for them (plus 3 more books I am working on right now). Every three months I get a check with my royalties from my US books and the translation rights for versions published elsewhere in the world, but without specifying which languages the books have been translated into.

For some reason I always forgot to ask for a list of the languages my books had been translated into, until a couple of months ago, when by acquisition editor made a search and returned this the following list. Every now and then, readers ask whether the book has been translated to their language, therefore I decided to post the information here.

Programming Microsoft Visual Basic 6 : English, Italian, Japanese, Korean, Spanish, Chinese (simplified, China), Chinese (traditional, Taiwan) + local English-language version in India.

Programming Microsoft Visual Basic .NET: English, Italian, French, Arabic, Japanese, Korean, Spanish, Chinese (simplified), Chinese (traditional) + local English-language versions in China and India.

Applied Microsoft .NET Framework Programming in Microsoft Visual Basic .NET (with Jeffrey Richter): English, Italian, Korean, Chinese (simplified), Chinese (traditional) + local English-language in China.

Programming Microsoft Visual Basic .NET Version 2003: English, Italian.

Practical Guidelines and Best Practices: English, Italiano, Russian + local English-language in China.

I can't help admitting that being translated into as many as ten languages is truly thrilling. :-)

11/22/2005 1:40:29 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, November 21, 2005

I am back from the Windows Professional Conference (Milan, Italy), for which I also server as the chairman for two of the four tracks. The conference touched virtually all the new features of Visual Studio and SQL Server 2005, plus tons of other topics. But that's another story.

When preparing the material for the conference I had to pack all my code samples in a zip file. Even after zipping them, I came up with a 7-8M file, which is a bit too much. So I was about to manually delete all the files that could be recreated by recompiling the projects. It's the same old story, that repeat itself with all conferences, books, articles for magazines and blog posts.

Instead of using the manual approach, this time I decided to write a tiny utility that does the work for me. It took (literally) two minutes to write a console utility that takes the path as an argument and recurses over that directory tree to remove all the folders named "bin" and "obj". If a delete operation fails, a message error is displayed. (This happens when an executable is running and is therefore locked by the operating system.) The code is especially concise thanks to an overload of the Directory.GetDirectories method (added in .NET 2.0) that returns all the directory in a tree.

Imports System.IO

Module Module1
  
Sub Main(ByVal args() As String)
      ' Use current directory if no argument has been specified
     
Dim rootDir As String = Directory.GetCurrentDirectory()
     
If args.Length > 0 Then rootDir = args(0)
     
' Read all the folder names in the specified directory tree
     
Dim dirNames() As String = Directory.GetDirectories(rootDir, "*.*", SearchOption.AllDirectories)
     
Dim errors As Integer = 0

      ' Delete all the BIN and OBJ subdirectories
     
For Each dir As String In dirNames
        
Dim dirName As String = Path.GetFileName(dir).ToLower()
           
If dirName = "bin" OrElse dirName = "obj" Then
              
Try
                 
Console.Write("Deleting {0} ...", dir)
                  Directory.Delete(dir,
True)
                  Console.WriteLine(
"DONE")
              
Catch ex As Exception
                  Console.WriteLine()
                  Console.WriteLine(
" ERROR: {0}", ex.Message)
                  errors += 1
              
End Try
           
End If
        
Next

         Console.WriteLine()
        
If errors = 0 Then
           
Console.WriteLine("All directories were removed successfully")
        
Else
           
Console.WriteLine("{0} directories couldn't be removed")
        
End If
    
End Sub
End
Module

In addition to using this tool from the command line, you can add it to the Tools menu, so that you can quickly delete all the files produced by compiling the current solution, by using this command:

               DELETEBINPATH $(SolutionDir)

where of course DeleteBinPath is the name you used when compiling the utility.

UPDATE: I have posted a new version of this utility in a more recent post.

11/21/2005 9:23:28 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
 
Get RSS/Atom Feed
RSS 2.0 | Atom 1.0
Search in the blog
Archive
<July 2009>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
Categories

Powered by: newtelligence dasBlog 1.8.5223.1