Wrap It Up
Call Into The .NET Framework From Existing Visual Basic 6.0 Apps
This article discusses:
|This article uses the following technologies:
Visual Basic 6.0, Visual Basic 2005 Express, .NET Framework
Code download available at:VBFusion05.exe(150 KB)
The Cheap and Easy Approach
Calling the .NET Framework
Now You Have It; Now You Don’t
Something Old and Something New
M any developers believe that if they are writing code in Visual Basic® 6.0, the vast world of the Microsoft® .NET Framework is off limits to them. But in this article, I’ll prove that isn’t true. I’ll show how you can easily leverage anything in the .NET Framework 2.0 from existing Visual Basic 6.0 applications through a technique known as Visual Basic Fusion. Make no mistake, I’m advocating nothing less than leaving some of your applications in Visual Basic 6.0. However, there’s no reason that the door to the .NET Framework class library must remain locked just because you choose not to upgrade to Visual Basic 2005.
So why not just upgrade? Tremendous resources are at your disposal if you choose to upgrade a Visual Basic 6.0 application to Visual Basic 2005. Visual Studio® provides a migration assistant that will automatically rewrite much of your Visual Basic 6.0 code. Microsoft has published a Visual Basic 6.0 to Visual Basic 2005 Migration Guide that includes an assessment tool to help determine the time and cost to upgrade your application. The Web is chock full of articles, how-tos, tips, and tricks on specific upgrading issues. There are even a few books on the topic. With so many resources available, some may question why any code should remain in Visual Basic 6.0. Why not just upgrade all of it?
In truth, upgrading code from Visual Basic 6.0 to Visual Basic 2005 can be a labor-intensive process, and the success of this endeavor hinges largely on two factors: code quality and developer experience with Visual Basic 2005 and the .NET Framework. Applications that are well architected and highly modular are easier to upgrade than applications that have become messy over time.
In the world of Visual Basic 6.0, many applications have been in production for years. Maybe these applications have needed feature enhancements on accelerated schedules. Maybe they have been programmed by numerous developers, some of whom did not have the time or patience to fully understand the code they were modifying. For whatever reason, programmers are often under pressure to get a project working and then move on. As a result, the code can get pretty convoluted over time. Sufficiently messy code is generally better off being rewritten rather than upgraded, and rewriting isn’t cheap or easy.
As for developer experience, upgrading a complex and mission-critical Visual Basic 6.0 application is a dreadful way to come up to speed on Visual Basic 2005. It’s important to have good experience with .NET before attempting to upgrade anything. There are several reasons for this. If you feed a Visual Basic 6.0 application down the chute of the migration wizard and turn the crank, what pops out the other side is a Visual Basic 2005 application that will not simply compile and run. The migration wizard will fling comments throughout the code marking locations where it was not able to automatically migrate. To fix the application and get it running, you need a solid understanding of Visual Basic 2005 and the .NET Framework. The code changes required range from simple and tedious to rewrites of entire application subsystems. So clearly, upgrading isn’t always easy.
With upgrading described as such a dismal prospect, you might wonder why anyone ever does it. Well, moving a code base to Visual Basic 2005 isn’t trivial, but it is ultimately the best way to get friction-free access to the full .NET Framework. I also find it much more productive to develop with the vast .NET Framework class libraries and with the superb productivity features provided by Visual Studio 2005. Visual Studio 2005 honestly makes Visual Studio 6.0 feel like something with a hand crank on the front. When it comes to upgrading, you’re going to have to make an application-by-application decision. Is it worth the effort? Is it feasible to do right now?
The Cheap and Easy Approach
Another option is to leave the application in Visual Basic 6.0 and just call into the .NET Framework as needed. Right in the box, the .NET Framework includes classes to ping IP addresses, detect changes to network connectivity, transmit files with FTP, access COM ports, play sounds, get operating system information such as physical memory and screen resolution, get user and domain information, read and write to the event log, access performance counters, start and stop services, and access the registry. The list goes on and on. The word "comprehensive" readily springs to mind when describing the .NET Framework 2.0 class library. By using the .NET Framework 2.0, you can likely eliminate dependencies on certain third-party controls and remove many Win32® API calls. Calling into the .NET Framework from Visual Basic 6.0 is easy and low risk. It’s also a good way to come up to speed on Visual Basic 2005 programming.
To call into the .NET Framework, as outlined in this article, you’ll need either Visual Studio 2005 or Visual Basic Express. Visual Basic Express includes all the toolbox controls, IntelliSense®, debugger, and many of the project items. It’s quite sufficient if you want to kick the tires and take Visual Basic 2005 for a spin. Plus, Visual Basic Express isn’t a trial version that stops working at some future date. Instead, it’s a pared down Visual Studio, designed to be used until the applications you’re building outgrow its capabilities.
You should also download the code associated with this article, particularly if you are using Visual Basic Express. Copy the included ComClass.zip file to your \My Documents\Visual Studio 2005\Templates\ItemTemplates\Visual Basic folder. As you’ll soon see, this project item is critical when calling into the .NET Framework from Visual Basic 6.0, and it’s not included with Visual Basic Express by default.
Calling the .NET Framework
Visual Basic 6.0 is great at calling into COM objects. However, the classes in the .NET Framework are not COM objects and are not directly callable from Visual Basic 6.0. Instead, the .NET Framework classes must be called through a wrapper. The wrappers are built using the COM Class item, and they behave like any other COM objects. The wrappers are callable from Visual Basic 6.0, Visual Basic Scripting Edition (VBScript), Visual Basic for Applications (VBA), or any other COM-compatible environment. The wrapper internals are constructed in Visual Basic 2005 and can call seamlessly into the .NET Framework (see Figure 1).
Figure 1** .NET Wrappers for Visual Basic 6.0 **
The construction of wrappers is a downright trivial task. These following steps will walk you through creating a COM callable wrapper for the Ping functionality in the .NET Framework 2.0.
- Start either Visual Studio 2005 or Visual Basic Express.
- Create a new Class Library project named FX20Wrapper.
- Select the Project menu, and then select Add New Item.
- Select the COM Class item template. For the name, enter NetworkWrapper, and click Add.
- Add the following function to the NetworkWrapper class:
Public Function Ping(ByVal hostNameOrAddress As String, _ Optional ByVal timeout As Integer = -1) If timeout >= 0 Then Return My.Computer.Network.Ping(hostNameOrAddress, timeout) Else Return My.Computer.Network.Ping(hostNameOrAddress) End If End Function
That’s it! With just a few lines of code, you’ve created a COM object that will let you ping every IPv4 address in the known universe. This function lets you ping by host name or IP address. You can also specify an optional timeout value. Imagine the possibilities. The only remaining step is to use the Build menu to compile the Visual Basic Express project. The IDE compiles your project and emits a DLL, which Visual Studio registers as a COM object.
Next, you can construct a Visual Basic 6.0 project that uses this wrapper.
- Start Visual Basic 6.0 and create a new Standard EXE project.
- Create a user interface like the one shown in Figure 2.
- On the Project menu, select References to add a reference to FX20Wrapper. Notice that your .NET project shows up just like any other COM object.
- In the click event of the Ping button, add the following code:
Private Sub Command1_Click() Dim network As FX20Wrapper.NetworkWrapper Set network = New FX20Wrapper.NetworkWrapper Text2 = Text2 & "Pinging " & Text1 & "..." DoEvents If network.ping(Text1) Then Text2 = Text2 & "Responded" & vbCrLf Else Text2 = Text2 & "Did not respond" & vbCrLf End If End Sub
- Run the application, enter an IP address, and click the Ping button. You should see a result like that shown in Figure 2.
Figure 2** Ping Test **
Now You Have It; Now You Don’t
Applications are increasingly expected to handle unexpected changes in network connectivity. Laptops unplug. Users travel from one wireless oasis to another. The operating system notifies the user every time the network comes and goes. Wouldn’t it be nice if the operation system also notified your application? With Visual Basic 2005, it does. The My.Computer.Network object fires an event whenever the network connectivity status changes. The code in Figure 3 shows how to enhance your NetworkWrapper class to provide this notification.
Figure 3 Wrapper for Network Availability Notification
Public Event NetworkAvailabilityChanged(ByVal IsAvailable As Boolean) Public Sub New() AddHandler My.Computer.Network.NetworkAvailabilityChanged, _ AddressOf My_NetworkAvailabilityChanged End Sub Public ReadOnly Property IsAvailable() As Boolean Get Return My.Computer.Network.IsAvailable End Get End Property Private Sub My_NetworkAvailabilityChanged(ByVal sender As Object, _ ByVal e As Microsoft.VisualBasic.Devices.NetworkAvailableEventArgs) RaiseEvent NetworkAvailabilityChanged(e.IsNetworkAvailable) End Sub
Notice that in the New subroutine (the constructor for the NetworkWrapper object), My.Computer.Network exposes a Net-workAvailabilityChanged event. This event is not directly COM-consumable. Instead, this event is hooked to a subroutine inside of the wrapper called My_NetworkAvailabilityChanged. From this routine, an event will be fired that is COM-consumable. This routine fires its own NetworkAvailabilityChanged event, which you can see contains a simple Boolean value to indicate the network status.
From the Visual Basic 6.0 side, this event is no harder to consume than a Button_Click event. The code for the Visual Basic 6.0 application is shown in Figure 4. With a tiny amount of code, your Visual Basic 6.0 applications can suddenly become network-aware. In an actual application that implements this wrapper, when the network is not available, the status bar might indicate its status as "Offline" and the ability to access network resources could be disabled.
Figure 4 Consuming NetworkAvailabilityChanged
Dim WithEvents network As FX20Wrapper.NetworkWrapper Private Sub network_NetworkAvailabilityChanged( _ ByVal IsAvailable As Boolean) ShowAvailable IsAvailable End Sub Private Sub Form_Load() Set network = New FX20Wrapper.NetworkWrapper ShowAvailable network.IsAvailable End Sub Private Sub ShowAvailable(IsAvailable As Boolean) cmdUploadData.Enabled = IsAvailable StatusBar1.SimpleText = IIf(IsAvailable, "Online", "Offline") End Sub
Something Old and Something New
The original RFC for FTP was published in 1973, and FTP remains one of the most popular protocols for transmitting files over the Internet. Microsoft has provided programmatic support for FTP in the past through the Microsoft Internet Transfer Control and the WinInet API. However, there has not been native support for FTP as part of the .NET Framework until recently. The .NET Framework 2.0 adds support for FTP through the WebClient, FtpWebRequest, and FtpWebResponse classes. Through these classes, you can perform a variety of FTP operations. The specific operations and the FTP commands that they map to are described by the WebRequestMethods.Ftp members, shown in Figure 5.
Figure 5 WebRequestMethods.Ftp Members
|AppendFile||Represents the FTP APPE protocol method that is used to append a file to an existing file on an FTP server.|
|DeleteFile||Represents the FTP DELE protocol method that is used to delete a file on an FTP server.|
|DownloadFile||Represents the FTP RETR protocol method that is used to download a file from an FTP server.|
|GetFileSize||Represents the FTP SIZE protocol method that is used to retrieve the size of a file on an FTP server.|
|ListDirectory||Represents the FTP NLIST protocol method that gets a short listing of the files on an FTP server.|
|ListDirectoryDetails||Represents the FTP LIST protocol method that gets a detailed listing of the files on an FTP server.|
|MakeDirectory||Represents the FTP MKD protocol method creates a directory on an FTP server.|
|PrintWorkingDirectory||Represents the FTP PWD protocol method that prints the name of the current working directory.|
|RemoveDirectory||Represents the FTP RMD protocol method that removes a directory.|
|Rename||Represents the FTP RENAME protocol method that renames a directory.|
|UploadFile||Represents the FTP STOR protocol method that uploads a file to an FTP server.|
|UploadFileWithUniqueName||Represents the FTP STOU protocol method that uploads a file with a unique name to an FTP server.|
The time required to download a file can vary greatly and is affected by file size and network bandwidth. To accommodate this, the FTP classes support both synchronous and asynchronous operations. Synchronous operations are the simplest to program, with a download requiring only a couple lines of Visual Basic 2005 code:
Dim w As New WebClient w.DownloadFile(address, fileName)
However, synchronous applications block (freeze) the application until the download operation has completed. While this behavior might be fine for command-line or batch operations, users rarely appreciate the experience of an application turning to stone without providing any indication of when it will reanimate. The FTP asynchronous operations perform the downloading on a separate thread and fire events notifying of the progress and download completion. Figure 6 shows a wrapper for the FTP classes that provides both synchronous and asynchronous operations.
Figure 6 Wrapper for FTP Functionality
Imports System.Net <ComClass(FTPWrapper.ClassId, FTPWrapper.InterfaceId, _ FTPWrapper.EventsId)> Public Class FTPWrapper #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 = "a9afe450-c10e-47cd-a004-f42dc975caaf" Public Const InterfaceId As String = _ "ec30e4cf-877c-4947-a093-715c2002aea0" Public Const EventsId As String = "ddffd280-bff9-40dd-9238-215232b90b11" #End Region Dim WithEvents w As New WebClient Dim totalBytes As Integer Public Event DownloadProgressChanged( _ ByVal bytesDownloaded As Integer, _ ByVal totalBytes As Integer) Public Event DownloadFileCompleted() Public Sub New() MyBase.New() End Sub Public ReadOnly Property IsBusy() As Boolean Get Return w.IsBusy End Get End Property Public Sub DownloadFile(ByVal address As String, ByVal fileName As String) w.DownloadFile(address, fileName) End Sub Public Sub DownloadFileAsync( _ ByVal address As String, _ ByVal fileName As String) If w.IsBusy Then Throw New ApplicationException( _ "FTPWrapper has not completed the previous operation. " & _ "One operation must complete before another can be initiated.") Else Dim f As FtpWebRequest = _ CType(WebRequest.Create(address), FtpWebRequest) f.Method = WebRequestMethods.Ftp.GetFileSize Using response As FtpWebResponse = _ CType(f.GetResponse(), FtpWebResponse) totalBytes = response.ContentLength End Using w.DownloadFileAsync(New System.Uri(address), "c:\readme.txt") End If End Sub Private Sub w_DownloadFileCompleted(ByVal sender As Object, _ ByVal e As System.ComponentModel.AsyncCompletedEventArgs) _ Handles w.DownloadFileCompleted RaiseEvent DownloadFileCompleted() End Sub Private Sub w_DownloadProgressChanged(ByVal sender As Object, _ ByVal e As System.Net.DownloadProgressChangedEventArgs) _ Handles w.DownloadProgressChanged RaiseEvent DownloadProgressChanged(e.BytesReceived, totalBytes) End Sub End Class
The FTPWrapper class builds on concepts illustrated thus far. First, this class is built using the ComClass project item template. This ensures that FTPWrapper will contain the necessary GUIDs for registration as a COM object. An instance of WebClient is dimensioned WithEvents at the class level. This allows all methods within the class to access this single instance, and it allows the FTPWrapper class to handle the WebClient DownloadProgressChanged and DownloadFileCompleted events.
The DownloadFile subroutine contains a single line of code and performs a synchronous download of a specific file. As described previously, calling this subroutine turns your application to stone while the file downloads.
DownloadFileAsync is a more versatile and interesting way to download a file. This subroutine begins by ensuring that there isn’t another asynchronous operation in progress. If there is, the subroutine throws an exception, which is the Visual Basic 2005 approximation of Err.Raise. Next, DownloadFileAsync uses the FtpWebRequest class to execute a SIZE FTP command, which returns the size in bytes of the requested file. The file size is needed in order to accurately display download progress. Finally, the WebClient DownloadFileAsync function is called. This function, which initiates the file download process in the background, also begins the regular firing of the DownloadProgressChanged event.
The WebClient DownloadProgressChanged event is not directly COM consumable, so this event is also wrapped. The handler for DownloadProgressChanged fires a new event, which passes simple integer arguments for bytes downloaded and total file size. This event is easily consumable by Visual Basic 6.0. Likewise, the DownloadFileCompleted event is wrapped and made consumable for Visual Basic 6.0.
The primary goal of creating wrappers is to make .NET Framework classes appear as typical COM objects, with easily usable properties, methods, and events. The FTPWrapper achieves this goal, and Figure 7 shows the Visual Basic 6.0 code needed to download a file using FTP.
Figure 7 Downloading a File
Dim WithEvents ftp As FX20Wrapper.FTPWrapper Private Sub btnDownload_Click() lblProgress.Visible = True prgProgress.Visible = True Command1.Enabled = False ftp.DownloadFileAsync txtAddress.Text, txtFileName.Text End Sub Private Sub Form_Load() Set ftp = New FX20Wrapper.FTPWrapper End Sub Private Sub ftp_DownloadFileCompleted() lblProgress.Visible = False prgProgress.Visible = False Command1.Enabled = True End Sub Private Sub ftp_DownloadProgressChanged(ByVal bytesDownloaded As Long, _ ByVal totalBytes As Long) prgProgress.Max = totalBytes prgProgress.Value = bytesDownloaded End Sub
Starting a download is as simple as dimensioning the FTPWrapper class and calling the DownloadFile or DownloadFileAsync subroutine. If you call DownloadFileAsync, then the FTPWrapper will fire DownloadProgressChanged events as the download proceeds and a DownloadFileCompleted event when the download is completed. The DownloadProgressChanged event receives the number of bytes downloaded so far and the total file size. This information is all that’s needed to provide a progress indication of the download. Figure 8 shows the Visual Basic 6.0 FTP client while a download is in progress.
Figure 8** FTP Client Implemented in Visual Basic 6.0 **
The code download for this article contains the wrapper class and sample applications shown in this article. To try out the samples, just download the code and execute the included install.bat file. This registers the wrapper class as a COM object. Then you can run any of the Visual Basic 6.0 samples.
Visual Basic 6.0 has become the new COBOL. This statement is made without malice toward either language. COBOL was enormously successful because it established itself as the language to use when writing mission critical mainframe applications. As the world went thick-client and GUI, Visual Basic assumed the mantle, showing itself to be arguably the best language for many business scenarios.
I can think of no mainstream business programming language that has truly gone extinct. Just as millions of lines of COBOL exist, and will for the foreseeable future, we will also be dealing with Visual Basic 6.0 for years to come. In fact, Microsoft has committed to shipping the Visual Basic 6.0 runtime on Windows Vista™, and running the Visual Basic 6.0 compatibility suite on Windows Vista to make a concerted effort to ensure that Visual Basic 6.0 applications just keep working.
But what is to be done with all this legacy code? Surely, it isn’t feasible to rewrite every line of it in Visual Basic 2005. Even if much of this code will eventually be migrated, it may not be possible to take that time and risk with a given application right now. Constraints dictate that many applications will continue their life on the Visual Basic 6.0 runtime. These applications need not ignore the .NET Framework and the productivity benefits that it brings.
You have seen how one tiny aspect of the .NET Framework, a few network classes, can be wrapped and exposed as COM objects, making their use from Visual Basic 6.0 trivial. Literally any portion of the .NET Framework can be wrapped and exposed in this way. Think of the .NET Framework as a gift from Microsoft of thousands upon thousands of classes. To use them, you need only supply the gift wrapping.
Scott Swigart spends his time consulting, writing, and speaking about converging and emerging technologies. Scott is the author of several books on .NET, a certified Microsoft trainer (MCT) and developer (MCSD), and a Microsoft MVP. You can contact Scott at firstname.lastname@example.org.