Visual Basic 6 Asynchronous File I/O Using the .NET Framework

 

Scott Swigart

March 2007

Applies to:
   Visual Basic 6
   Visual Studio 2005

Summary: Learn how to pass information to a background thread from an existing Visual Basic 6 application and how the results of the background work can then return from the background thread to the Visual Basic 6 application. (7 printed pages)

Download the associated Asynch File IO.exe sample code.

Contents

Overview
First, the Easy Part
Creating the Asynchronous Visual Basic 2005 Component
Conclusion

Overview

There are many applications that need to monitor directories and process incoming files. Files could appear as data exported from a legacy system, as FTP or HTTP uploads, or a variety of other sources. I set out to build an application in Visual Basic 6 that would process incoming files. The Visual Basic 6 application needed to satisfy the following requirements:

  1. A directory monitored on a background thread: The application needed to remain responsive and unblocked while waiting for files to appear.
  2. File contents loaded on a background thread so that the application would not be blocked while large files were being loaded.
  3. Some processing of the file contents.

If you're working with Visual Basic 6, you know that its quite capable of reading files. However, doing this kind of work on a background thread is beyond its core capabilities. On the other hand, Visual Basic 2005 fully supports coding background operations and you can easily call Visual Basic 2005 code from Visual Basic 6 applications.

This article will show how a Visual Basic 2005 component was created to perform the background operations to achieve the above requirements, as well as how this component was easily used from a Visual Basic 6 application.

First, the Easy Part

By putting the background work in a separate component, the core Visual Basic 6 application becomes simple and easy to follow. The entire application is shown in Listing 1**.**

Listing 1: Visual Basic 6 application

Option Explicit

Dim WithEvents dirWatcher As DropDirMonitor.DirWatcher

Private Sub Form_Load()
    Randomize

    txtDropDir.Text = App.Path & "\Drop"
    Set dirWatcher = New DropDirMonitor.dirWatcher
End Sub

Private Sub cmdStart_Click()
    dirWatcher.Init txtDropDir.Text
End Sub

Private Sub cmdGenDataFile_Click()
    Dim i As Integer
    
    Dim fileName As String
    fileName = txtDropDir.Text & "\" & Rand(1, 100000) & ".dat"
    
    Open fileName For Output As #1
    
    Print #1, "ProductID,Quantity,Price"
    
    For i = 1 To Rand(5000, 30000)
        Print #1, Rand(1, 100) & "," & Rand(1, 20) & "," & Rand(1, 50)
    Next
    
    Close #1
End Sub


Private Sub dirWatcher_ProcessingFile(ByVal fileName As String)
    fileName = Right(fileName, Len(fileName) - InStrRev(fileName, "\"))
    Text1.Text = Text1.Text & "Processing:" & fileName
    DoEvents
End Sub

Private Sub dirWatcher_FileContentsAvailable(ByVal contents As String)
    Dim lines() As String
    lines = Split(contents, vbCrLf)
    
    Dim total As Double
    Dim line As Variant
    For Each line In lines
        Dim items() As String
        items = Split(line, ",")
        If UBound(items) > -1 Then
            If IsNumeric(items(1)) Then
                total = total + (CDbl(items(1)) * CDbl(items(2)))
            End If
        End If
    Next
    
    Text1.Text = Text1.Text & ", Total = " & FormatCurrency(total) &
vbCrLf
End Sub

Public Function Rand(ByVal Low As Long, ByVal High As Long) As Long
    Rand = Int((High - Low + 1) * Rnd) + Low
End Function

The Visual Basic 6 application creates an instance of the DropDirMonitor.DirWatcher component. This component will fire events when a new file shows up in a specified "drop directory." The events will let the Visual Basic 6 application know the name of the file and will provide the loaded file contents.

The Form_Load event in Visual Basic 6 will create a new instance of the DirWatcher class. When the user clicks the Start button, the application will initialize the DirWatcher to watch a specific directory for new files. When files appear in that directory, the ProcessingFile event will fire. This informs the Visual Basic 6 application that a new file has been detected for processing. The DirWatcher will then load the file contents into memory and fire the FileContentsAvailable event. Because the DirWatcher works on a background thread, if loading a very large file takes too long, the Visual Basic 6 application does not stall.

When the FileContentsAvailable event fires, it passes the file data to the Visual Basic 6 application for processing. Figure 1 shows the sample Visual Basic 6 application as it processes new files.

Bb381704.vb6asynch(en-US,VS.80).gif

Figure 1. Visual Basic 6 application processing files

In this sample application, the file that appears in the drop directory will contain thousands of lines of CSV data. When the Visual Basic 6 application is given the contents, it parses the data and calculates a total.

Creating the Asynchronous Visual Basic 2005 Component

With Visual Basic 2005, it's easy to create a component that can be called from Visual Basic 6. Simply create a new project and select ClassLibrary as the project type. On the Project menu, select Add New Item and add a COM Class to the project. When you compile the class library, it will also be registered as a COM object. You can reference it from Visual Basic 6, as well as call the functions in your COM classes.

The code download for this article includes a Visual Basic 2005 solution called DropDirWatcher. This is the Visual Basic 2005 code that watches for new files to appear, loads the file contents into memory, and makes the file contents available to the Visual Basic 6 application. This project contains a single file called DirWatcher.vb, which is a COM Class. The beginning of this file is shown in Listing 2.

Listing 2: Start of the DirWatcher class

<ComClass(DirWatcher.ClassId, DirWatcher.InterfaceId,
 DirWatcher.EventsId)> _
Public Class DirWatcher
    Inherits Control

#Region "COM GUIDs"
    ' These  GUIDs provide the COM identity for this class 
    ' and its COM interfaces. If you change them, existing 
    ' clients will no longer be able to access the class.
    Public Const ClassId As String = "db5b9156-4aa7-4a5d-a32d
-ceb6af2dc3e2"
    Public Const InterfaceId As String = "2c22921f-2357-4215-9759
-0ba865a65d7a"
    Public Const EventsId As String = "1c1e49dc-d641-41b3-9394
-b003de631c4d"
#End Region

    Private WithEvents mFileSystemWatcher As New FileSystemWatcher

    Private mDirectoryToWatch As String

    Private filesToProcess As New List(Of String)

    Public Sub New()
        MyBase.New()
        Me.CreateHandle()
    End Sub

    Public Sub Init(ByVal directoryToWatch As String)
        mDirectoryToWatch = directoryToWatch
        mFileSystemWatcher.Path = mDirectoryToWatch
        mFileSystemWatcher.EnableRaisingEvents = True

        For Each file As String In Directory.GetFiles(directoryToWatch)
            filesToProcess.Add(file)
        Next

        ProcessNextFile()
    End Sub

    Private Sub mFileSystemWatcher_Created(ByVal sender As Object, _
        ByVal e As System.IO.FileSystemEventArgs) _
        Handles mFileSystemWatcher.Created

        If Me.InvokeRequired Then
            If e.ChangeType <> WatcherChangeTypes.Created Then Return

            Me.Invoke(New EventHandler(Of FileSystemEventArgs) _
                (AddressOf mFileSystemWatcher_Created), sender, e)
        Else
            filesToProcess.Add(e.FullPath)
            ProcessNextFile()
        End If
    End Sub

The .NET Framework contains a class known as the FileSystemWatcher. This class will monitor a directory and fire events when any changes are detected. In the Init method, the FileSystemWatcher is configured with the directory to watch and enable events. Now, any time a file is added to that directory, the mFileSystemWatcher_Created event will fire.

The mFileSystemWatcher_Created event may not fire on the same thread as the main Visual Basic 6 application. For this reason, you see that the mFileSystemWatcher_Created event contains an odd looking "If" statement. The purpose of this statement is to detect if the event has fired on a background thread. By checking Me.InvokeRequired, the code is able to determine whether it is on the main thread or a background thread. If InvokeRequired is true, then the function calls back to itself using Me.Invoke. This will martial the arguments to the main thread where the new file is added to the list of files that need to be processed.

To load in the file contents asynchronously, a BackgroundWorker component is used. This makes it easy to work on a background thread. The ProcessNextFile routine, shown in Listing 3, starts the BackgroundWorker on the first file to process.

Listing 3: Visual Basic.NET Component Starts File Processing

    Private Sub ProcessNextFile()
        If filesToProcess.Count = 0 Then Return
        If Me.Cancel = True Then Return

        If Not mBackgroundWorker.IsBusy Then
            Dim fileToProcess As String = filesToProcess(0)
            filesToProcess.RemoveAt(0)
            If File.Exists(fileToProcess) Then
                mBackgroundWorker.RunWorkerAsync(fileToProcess)
            End If
        End If
    End Sub

This routine starts the file processing by calling BackgroundWorker.RunWorkerAsync, passing in the path to the first file to process. This causes the DoWork event associated with BackgroundWorker to fire on a background thread. Listing 4 shows how the file is loaded in this event.

Listing 4: Background work to load the file

    Private Sub mBackgroundWorker_DoWork(ByVal sender As Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) _
    Handles mBackgroundWorker.DoWork
        Using sr As New StreamReader(File.Open( _
            e.Argument, FileMode.Open, _
            FileAccess.Read, FileShare.None))
            
            mBackgroundWorker.ReportProgress(0, e.Argument)
            e.Result = sr.ReadToEnd()
        End Using
        File.Delete(e.Argument)
    End Sub

The routine opens the file for reading and then reads the entire file contents into memory. If the file is large, the application remains responsive because the DoWork function is running on a background thread. When the file contents are read, they are placed in the Result property of the event argument. In this way, the BackgroundWorker does the work of marshaling the file contents back to the main thread. When DoWork finishes, the BackgroundWorker fires the RunWorkerCompleted event, passing it the file contents as shown in Listing 5.

Listing 5: File contents have been loaded, notify the Visual Basic 6 application

    Private Sub mBackgroundWorker_RunWorkerCompleted( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
    Handles mBackgroundWorker.RunWorkerCompleted

        If e.Result IsNot Nothing Then
            RaiseEvent FileContentsAvailable(e.Result)
        End If

        ProcessNextFile()
    End Sub

This routine raises the FileContentsAvailable event. This event is handled by the Visual Basic 6 application. The result is that the Visual Basic 6 application can remain responsive and handle any user operations, and when a file appears in the drop directory, the Visual Basic 6 application simply receives an event containing the file contents.

Note   The full solution code is available for download from Microsoft: https://download.microsoft.com/download/3/6/7/367DCF3B-A23E-41BA-BF6A-7E8BFF4FF476/Async File IO.EXE.

Download the solution and use Visual Studio 2005 (or Visual Basic Express) to compile the DirWatcher component. You can then run the Visual Basic 6 application, which uses this component.

Conclusion

While this article focuses on background file processing, it really provides a framework that allows you to perform practically any operation on a background thread. This article shows how information can be passed to that background thread from an existing Visual Basic 6 application and how the results of the background work can be returned from the background thread to the Visual Basic 6 application.

© Microsoft Corporation. All rights reserved.