Moving Your Legacy Hardware Code to Visual Basic 2005

 

Bill Sempf

December 2005

Applies to:
   .NET Framework 2.0
   Visual Basic 2005
   Visual Basic 6.0

Summary: Bill Sempf shows you what's new with serial and parallel communication in Visual Basic 2005 and what it takes to migrate your legacy Visual Basic 6.0 hardware code to Visual Basic 2005. (13 printed pages)

Contents

The Ins and Outs of Coding for Devices
Modems and Serial Devices
Printers and Parallel Devices
Video Monitor Control
Communication with IrDA and Other Network Devices
Conclusion

While Visual Basic has never been a language for hardware drivers, it has often been used for control of the communication ports. Serial and parallel communication is a feature of the operating system, not the language, so communication usually takes place through direct communication with kernal32 through an ActiveX control like MsComm or third-party component.

Upgrading applications like these can be a challenge. While the general strategy for hardware communication is the same for Visual Basic 2005 as it is for Visual Basic 6.0, the names have been changed "to protect the innocent," and the classes you need to get the job done can be hard to find.

In this article, I will take you through common solutions for device coding. I will cover serial devices like modems, parallel devices like printers, video monitors and infrared ports in terms of communication with Visual Basic 2005. You will get to see how conversion from one platform to another can be quite a pleasant experience, ending with much more manageable code.

The Ins and Outs of Coding for Devices

Visual Basic 6 required a programmer to go right to the operating system to communicate directly with the PC. The 2.0 version of the .NET Framework—which is used in Visual Basic 2005—has a lot of the hardware control "baked in." That means that you can so in managed code that for which you used to have to create Win32 calls.

Because Visual Basic isn't often used for hardware control, there is little written about it in the documentation or the MSDN library. You should know that this isn't because the classes for hardware control aren't powerful. It is just because MSDN articles are written for the majority of programmers, and the majority of programmers aren't using Visual Basic to control hardware.

That isn't to say that Visual Basic 2005 has a perfect representation of the hardware language. It doesn't. It does have a significantly improved model, however; and I will cover what you can and can't do here.

For instance, serial port communication is now completely covered. You no longer need to go to the Win32 libraries for practically anything when using the serial ports. The SerialPort class will take care of most of what you need. Printers, specifically, are handled as well. The video driver system is also well handled in Visual Basic 2005. Only when you get into the more esoteric parts of hardware control do you need to get into unmanaged Interop classes.

To further simplify, the My object contains a remarkable number of classes that will take care of the PC hardware. My.Computer does a remarkable job of exposing much of what the host PC can do. Keyboards, mice, monitors, modems, and other hardware features are in IntelliSense, making the effort almost trivial.

Modems and Serial Devices

The serial port connector in Visual Basic 6 is the MSComm control. You can use the MSComm control in .NET through Interop, but you need to have Visual Basic 6 installed on the development machine to avoid licensing problems. You can also directly talk to kernel.dll using pinvoke, but that is really messy (though there is an example elsewhere on MSDN).

Instead, I recommend the System.IO.Ports.SerialPort class in Visual Basic 2005. It has striking similarity to the old MSComm object, but it is managed code, so comes with all do the benefits of that. The layout of the methods looks something like this:

Old MSComm Members New SerialPort Class Members
OnComm Event DataReceived event
CommPort Property PortName property
Settings property BaudRate, Parity, DataBits and StopBits properties
PortOpen property IsOpen property
Input property Read Method
Output property Write Method

Example: Getting to a Modem

Writing hardware code without the hardware is tough. Noah Code (a Microsoft program manager) has a great idea, using two USB to Serial converters and a null modem between them to test on your own machine. When you plug a USB to serial converter into your PC it will get a COM Port number, and if you plug two in you get two port numbers. If you link them with a null modem this is perfect for sending and receiving serial messages!

To send information to the serial port of your choice you need to open a new SerialPort object. The constructor for the SerialPort class—that's the New method that gets run when you instantiate a new object—accepts all of the values that you need to set up the connection. You can also use the appropriate properties to set up a connection. For instance, let's look at a 56,000 bps device on COM port 4. Using the constructor, you would instantiate the control like this:

Dim mySerialPort as SerialPort = new SerialPort("COM4", 56000, Parity.None, 8, StopBits.One)

Because of the proliferation of available properties as part of the new object, you also have the option to create the new SerialPort then set the properties later. There is no functional difference, but it is good to understand the different thought process that went into creation of this object.

        Dim mySerialPort As SerialPort = New SerialPort()
        With mySerialPort
            .PortName = "COM4"
            .BaudRate = 56000
            .Parity = Parity.None
            .DataBits = 8
            .StopBits = StopBits.One
        End With

Wondering what the Parity and StopBits enumerators do? Nothing special. Parity.None is just a constant for 0—they are just there to add readability and prevent magic numbers in your code. It is kind of nice, if you ask me. Either way these two segments of code are the same, and it is much less cryptic that the strange connection strings required by the MSComm objects.

Now you have a SerialPort object—what can you do with it? You can Send and you can Receive, just like the MSComm objects.

As you can see in this example, the Send part of this process is very similar to the Send functions on the MSComm control.

Visual Basic 6

'This is assuming you have an MSComm control on your form called myMsComm
myMsComm.Settings = "9600,N,8,1"
myMsComm.CommPort = 2 
myMsComm.PortOpen = True
myMsComm.Output = "Hello World!"
myMsComm.PortOpen = False

Visual Basic 2005

Dim mySerialPort As SerialPort = New SerialPort("COM4", 56000, Parity.None, 8, StopBits.One)
With mySerialPort
    .Open()
    .Write("Hello world!")
    .Close()
End With

Nearly exactly the same, and it isn't a coincidence. The code required to use kernel.dll to write to the serial ports under Visual Basic .NET 2003 was 80 lines long. This needed to be done.

How about the reading side? Here things get really nice on the Visual Basic 2005 side. In Visual Basic 6 we needed to handle buffer sizes, and loop, and wait, and generally had a bad time. Because Visual Basic 6 didn't think in streams, we had to hack out a solution. For that reason, Visual Basic 6 code tended to look like this when we had to receive data from the port—it was heavily device-dependent.

    'Code from MSDN
    Dim txtBuff As String
    Dim i As Integer
    Dim c As Long
    Dim BuffLength


    MSComm1.InBufferCount = 0 ' clear inBuffer

    Do ' wait for incoming bytes
      DoEvents
    Loop Until MSComm1.InBufferCount > 0
    txtBuff = MSComm1.Input
    BuffLength = Len(txtBuff) ' number of received bytes
                                            ' (<= 4 bytes)
    bstem = Asc(Mid(txtBuff, 1, 1)) ' get module number
    bbuff(0) = Asc(Mid(txtBuff, 2, 1)) ' get packet size

    If BuffLength < 2 + bbuff(0) Then ' if received bytes < packet size
        MSComm1.InBufferCount = 0 ' do it again
        Do
           DoEvents
        Loop Until MSComm1.InBufferCount > 0
        txtBuff = txtBuff & MSComm1.Input
    End If

    For i = 3 To Len(txtBuff) ' write received data
        c = Asc(Mid(txtBuff, i, 1)) ' to array bbuff()
        bbuff(i - 2) = c
    Next

Now, we can just write a new event handler against the DataReceived event of the SerialPort object we have floating around. This example just writes it to the console, but you might want to write it to a database, or a string array, or the screen in a Label control.

    Private Sub mySerialPort_DataReceived(ByVal sender As Object, _
         ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) _
          Handles mySerialPort.DataReceived
        Console.Write(mySerialPort.ReadExisting())
    End Sub

There is little question as to which block of code is more efficient here. Because Visual Basic 2005 handles operating system events better through the .NET Framework, it can handle interrupted serial input with some grace. Visual Basic 6 could not.

What about USB?

Certain USB devices are handles separately as part of the .NET Framework. Flash Drives, for example are handles by System.IO.DriveInfo, and are of DriveType Removable.

You will eventually get into the sticky problem of hardware drivers, but there is a general rule. Any device that uses the default Microsoft driver and has a COM port will be available for use by the SerialPort class. If you need to get to a USB device that doesn't understand RS232, for example, you will have to pinvoke the Win32 API again, with HID.dll and kernel.dll. Check out the OpenConnection(), GetExternalHubName(), and GetNameOf() methods. There is more information at the Intel Web site on this.

Parlor Tricks

There are a few really cool things that you will find in the SerialPort control. Because it is more generalized than the MSComm control, it can handle input from a variety of devices with some grace. This is made obvious not only in the name of the control, but also in some of the members of the class.

  • Use the BaseStream property to get ahold of the underlying object that has the stream of information going to and coming from the device. You can make modifications before your code gets to the raw data.
  • Along those same lines, you have Encoding options, to determine the pre- and post- transmission encoding of the stream.
  • The MSComm control just has an Input property. The SerialPort class has six read methods, including Read, ReadByte, ReadChar, ReadExisting, ReadLine, and ReadTo.
  • The SerialPort class implements the IContainer interface.
  • You have access to an ErrorReceived event and a PinChanged event. These will make your hardware controllers much more stable in the field.

Printers and Parallel Devices

Printing in Visual Basic 6 runs to two options—the Printers collection, and calling the Win32 APIs through winspool.drv. These leave you with either a fair amount of power, or fair flexibility, but a monster amount of code.

The winspool.drv API members include OpenPrinter, StartDocPrinter, StartPagePrinter, WritePrinter, EndPagePrinter, EndDocPrinter and ClosePrinter, among others. As with many API calls there is a lot of functionality here, but configuration is problematic. Finding the printer you want is impossible, and often a component that uses the winspool driver resorts to using the default printer, severely reducing the flexibility of the PC.

The Printers collection and Printer object is another story—slightly less power and slightly more flexibility. Normally a program loops through the collection to find an object that matches the criteria needed—the first printer in the collection with 8x14 paper, for instance. This obviously has its problems, but once you have a printer, you can do most anything you need with it.

Visual Basic 2005 uses neither of these methods. Instead, the goal is to have the user choose the printer, just like a real program. After that, you write to the stream generated, just like most other Windows platform languages. This control, which replaces the old Printers collection in functionality if not intent, is available from the Windows Forms toolbox and is called the PrintDialog control.

ms364066.vb05legacyhardware_01(en-US,VS.80).gif

One of the more common functions for the average Visual Basic program is to write fixed-width reports to a line printer, replicating COBOL program functionality. When Visual Basic 6 was in force, the printer was usually on a printer port of the PC. These days, the printer can be halfway around the world. That's why the PrintDialog is so useful.

Once you have the printer, the layout of the PrintDocument object is similar to the Printer object in Visual Basic 6, with the usual .NET twist. The PrintDocument object provides the ability to write Graphics objects or stream Text directly to the printer. Getting text to the printer used to require that you sliced your code up into lines and used a Win32 call. Now the Stream object and the .NET Framework provide a better solution.

Visual Basic 6

'This is assuming there is a Printer object set up in the Form designer.
Private Declare Function WritePrinter Lib "winspool.drv" _
 (ByVal hPrn As Long, pBuf As Any, ByVal cdBuf As Long, pcWritten As Long) As Long
Private Declare Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" _
 (ByVal pPrinterName As String, phPrn As Long, pDefault As Any) As Long
Private Declare Function StartPagePrinter Lib "winspool.drv" (ByVal hPrn As Long) As Long

'Remember, textToWrite is only one line of text
Public Function WriteTextToPrinter(ByVal textToWrite as String) as Long

        Call OpenPrinter(Printer.DeviceName, printerNumber, ByVal 0&)
        Printer.FontName = "courier"
        Printer.FontSize = 12
        lTemp = printerNumber
        If lTemp = 0 Then
           error = GetLastError
            GoTo Exit_Err_Routine
        End If
        Call StartPagePrinter(printerNumber)
   Call WritePrinter(printerNumber, textToWrite, Len(textToWrite), Written)
   
   WriteTextToPrinter = Written
   
End Function

Visual Basic 2005

Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, _
 ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
Dim lineCounter As Integer = 0
Dim linesPerPage As Single = 0
Dim line As String = String.Empty
Dim myFont As Font = New Font("Times New Roman", 12)
Dim printStream As StreamReader = New StreamReader("c:\text.txt")
Dim topOfPage As Single = 0

' Calculate the number of lines per page.
linesPerPage = e.MarginBounds.Height / myFont.GetHeight(e.Graphics)

' Iterate over the file, printing each line.
While lineCounter < linesPerPage
    line = printStream.ReadLine()
    If line Is Nothing Then
   Exit While
    End If
    topOfPage = 100 + lineCounter * myFont.GetHeight(e.Graphics)
    e.Graphics.DrawString(line, myFont, Brushes.Black, 100, _
   topOfPage, New StringFormat())
    lineCounter += 1
End While

End Sub

While the old PrintForm method is no longer supported, there are other, better ways to get your images to the printer. Now thanks to the rest of the System.Drawing namespace, you can send an image directly to the printer—even one you made on the fly!

    Private Sub document_PrintPage(ByVal sender As Object, _
   ByVal e As System.Drawing.Printing.PrintPageEventArgs) _
       Handles PrintDocument1.PrintPage

        Dim printFont As New System.Drawing.Font _
            ("Arial", 35, System.Drawing.FontStyle.Regular)
        e.Graphics.DrawString("This text is being written to the printer.", _
            printFont, Brushes.Black, 10, 10)
        'Underline it for fun!
        e.Graphics.DrawLine(Pens.DarkCyan, 10, 40, 200, 40)

    End Sub

The trick to this is that the PrintPageEventArgs comes back with a Graphics object that can be referred to and supports the entire set of Draw... methods that usually come with the Graphics class.

Unfortunately, for other parallel port devices you are left looking for the local driver and calling the Win32 API again. If you reference the IO.DLL library in your application (it is a COM library, so it will generate an Interop component), you can read and write to non-printer devices on the parallel port.

Private Declare Sub PortWordOut Lib "IO.DLL" (ByVal Port As Integer, _
ByVal Data As Integer)
PortWordOut(23, TextBox1.Text)

You guessed it—this is pretty much the same as the Visual Basic code of the same kind.

Video Monitor Control

Unlike modems and printers, there isn't much you can do in any language other than C++ to directly interact with a video card or monitor. Largely, Visual Basic 6 and Visual Basic 2005 restrict direct interaction with the monitor to checking the size of the resolution.

In Visual Basic 6, one object, the Screen object, handles most all of this functionality. What's more, it handles it in the imaginary measurement of Twips, which have thankfully been replaced in Visual Basic 2005 with the more universal Pixels.

While this is convenient in some regards, it hides functionality that you may need elsewhere in a less-than-obvious place. For most monitor control functionality, though, it worked pretty well. One of the more common functions was to place something on the screen at a precise location. You can do that in Visual Basic 2005!

Visual Basic 6

Dim xCenter, yCenter As Single
xCenter = ((Screen.Width / 2) - (Form1.Width / 2))
yCenter = ((Screen.Height / 2) - (Form1.Height / 2))
Form1.Move xCenter, yCenter

Visual Basic 2005

Dim xCenter, yCenter As Single
xCenter = ((My.Computer.Screen.Bounds.Width / 2) - (Me.Bounds.Width / 2))
yCenter = ((My.Computer.Screen.Bounds.Height / 2) - (Me.Bounds.Height / 2))
Me.Location = New Point(xCenter, yCenter)

Rarely does anyone do anything else, but even replicating this can be a problem for many developers, because everything about the screen is in a different place, it seems, in Visual Basic 2005.

Generally speaking, the Visual Basic 6 Screen object properties map to specific parts of the Framework. If you are looking to exactly replicate things, here is the grid:

Visual Basic 6.0 Visual Basic 2005
ActiveControl System.Windows.Forms.Application.ActiveForm.ActiveControl
ActiveForm System.Windows.Forms.Application.ActiveForm
FontCount No equivalent. The behavior for enumerating fonts is different. For more information, see Font Changes in Visual Basic .NET.
Fonts System.Drawing.FontFamilies

Note   The behavior for enumerating fonts is different. For more information, see Font Changes in Visual Basic .NET.

Height System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height
MouseIcon No equivalent. For more information, see Cannot set a custom MousePointer.
MousePointer System.Windows.Forms.Cursor

Note   In Visual Basic 6.0, changing Screen.MousePointer changed the cursor's appearance until Screen.MousePointer was changed again. In Visual Basic .NET, it remains changed only until Windows messages are processed again (until the next DoEvents call or until the program's processing is done).

TwipsPerPixelX No equivalent. In Visual Basic .NET, coordinates are in pixels and twips are not used as a unit of measurement.
TwipsPerPixelY No equivalent. In Visual Basic .NET, coordinates are in pixels and twips are not used as a unit of measurement.
Width System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width

At first glance, this is really a confusing mess. At second glance, though, it is pretty sensible. The Screen class is a property of the form. Why? Multiple monitors. I am sitting on a multiple monitor machine now, and I just about toasted my Visual Basic 6 sample app trying to center a form. Fonts, too, should be part of a larger entity. All in all, it makes a lot of sense.

Communication with IrDA and Other Network Devices

A quick note on IrDA. In Visual Basic 6, IrDA is controlled by MScomm, like a modem, and it has to have a readable COM port. Reading and writing to an IrDA device is similar to that in the earlier section, Example: Getting to a Modem. The protocol is a little rough, though, and a lot of people have resorted to third-party InfraRed Device controls, like ActiveSMS.

In the .NET Framework, IrDA is a network device, handled by System.Net.Sockets. This extremely handy namespace will take care of all of your network device woes in one neat package. According to the MSDN documentation for the Sockets namespace, these are the network protocols that are supported:

  • AppleTalk protocol
  • Native ATM services protocol
  • Banyan protocol
  • CCITT protocol, such as X.25
  • MIT CHAOS protocol
  • Microsoft Cluster products protocol
  • DataKit protocol
  • Direct data link protocol
  • DECNet protocol
  • European Computer Manufacturers Association (ECMA) protocol
  • FireFox protocol
  • NSC HyperChannel protocol
  • IEEE 1284.4 workgroup protocol
  • ARPANET IMP protocol
  • IP version 4 protocol
  • IP version 6 protocol
  • IPX or SPX protocol
  • IrDA protocol
  • ISO protocol
  • LAT protocol
  • MAX protocol
  • NetBIOS protocol
  • Network Designers OSI gateway enabled protocol
  • Xerox NS protocol
  • OSI protocol
  • PUP protocol
  • IBM SNA protocol
  • Unix local to host protocol
  • VoiceView protocol

Heck, I don't even know what some of those are and I have been around for a long time.

Anyway, the class that makes this all happen is in the System.Net.Irda namespace, which is only supported on PocketPCs and Media Edition of XP. Nonetheless, there is some power here.

Dim myIrdaEndpoint As IrDAEndPoint = New IrDAEndPoint(irDevices(0).DeviceID, "IrDA Test")
Dim myIrdaListener As IrDAListener = New IrDAListener(myIrdaEndpoint)
myIrdaListener.Start()
Dim myIrdaClient As IrDAClient = myIrdaListener.AcceptIrDAClient()
MessageBox.Show(String.Format("Connected to {0}", myIrdaClient.RemoteMachineName))
myIrdaListener.Stop()

It remains to be seen if the IrDA classes will be able to be ported to a laptop running Vista—I'll leave that as an exercise for the reader.

Conclusion

Instead of a normal conclusion, I'll put in my plug for the My.Computer namespace. I handled communication devices here, but there is more to computer hardware than that, and we all know it.

The My object was designed as a "speed dial" into the .NET Framework and to give a boost to developer productivity. Many of the simpler devices that connect to your PC can be maintained using the My.Computer class.

  • My.Computer.Audio—Media and speakers
  • My.Computer.Info—Memory and Processor
  • My.Computer.Keyboard—Configuration of the keyboard
  • My.Computer.Mouse—Pointer location and more
  • My.Computer.Network—Shortcut to the System.Net namespace classes
  • My.Computer.Ports—Access to the serial ports at the beginning of the article
  • My.Computer.Screen—Similar to the vaunted Visual Basic 6 Screen object I discussed

In some ways, the My.Computer object brings the simple access to hardware that Visual Basic 6 provided, while the .NET Framework below provides the power of an integrated platform. Jay Roxe mentioned that Visual Basic 2005 brings the 'heart and soul of Visual Basic 6 to .NET' and in this way, at least, he is right.

© Microsoft Corporation. All rights reserved.