Home
Archives
About us...
Advertising
Contacts
Site Map
 

ruby in steel

 

VB .NET COMMUNICATIONS #1

In a new series we move to VB .NET and find out how to improve on the good old Visual Basic 6 MSComm serial port control
by Dermot Hogan

Requirements:
VB .NET (.NET 1.1)

 

Download The Source Code:
vb4src.zip

 

See also part two of this series

Over the last three months, I’ve described how to use a serial communications port from Visual Basic 6. I was interested in this was for the simple reason that I was getting a little frustrated by the limitations of the MSComm control. In particular, I was really inconvenienced by the way that, in order to alter the state of a control signal like DTR, you had to close the port and then re-open it. However, there’s another related limitation: if you close the port, the control signal, DTR, is dropped. This is normal behaviour. Usually, if you want to close a port, you really do want to signal to the other end that you’ve done so.

But not always. One situation where you might not want to do this is where you dial-up a remote computer using a set of standard modem commands and, once the remote computer has answered, pass the communication channel to another program or control. But using the standard MSComm control, you’re stuck.


This month we'll design a VB .NET control. This is done in a similar way to designing a standard form.
Just drag existing controls from the toolbox

In my first set of Visual Basic communications articles, it was virtually impossible to signal events such as a DSR state change to the calling program efficiently. Either you had to ‘poll’ the control signal – that is look, at it every few milliseconds or so – or take your Visual Basic life in your hands and try to use threads. From bitter experience, I can confirm that the second course is not guaranteed to result in stable programs that work!

The big problem is that Visual Basic 6 was really only designed to do one thing at a time. But communications programming is, due to its very nature, ‘asynchronous’ – events happen in a sequence that cannot be predicted. So trying to work round the limitations of the standard MSComm control wasn’t too successful. In Visual Basic .NET, on the other hand, rolling your own communications control has more than a chance of success - it’s actually quite doable.

First Create Your Control

Let’s start off by creating a simple control. Begin a new project, call it ‘comm’ and from the project menu click ‘Add User Control’. Set the name of the new control to TestControl and save.


To create a new user control, select the User Control template from the Add New Item menu

You’ll now see a blank form designer looking much the same as a standard form. Add a button, Button1, and a text box, TextBox1. You’ve now built a simple user control with two normal Windows controls on it – it’s as easy as that. Of course, all the hard work is hidden in the class structure, indicated by the statement:

Inherits System.Windows.Forms.UserControl

...which comes immediately after the class declaration. Unlike in Visual Basic 6, a user control is a proper class with properties and methods inherited from a template class, UserControl. This makes life a lot simpler – all the ‘ambient’ and ‘extender’ stuff has been absorbed into a logical class structure which makes it easy to deal with how you want a control to look and behave.

Before closing the control, we’ll add a VB .NET event that will handle the button click event:

Event Button1ClickEvent(ByVal c As Integer)
Private Button1ClickCount As Integer
Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
   Button1ClickCount += 1
   RaiseEvent Button1ClickEvent(Button1ClickCount)
End Sub

Here, I’ve used the RaiseEvent keyword to signal an event declared as having a single integer argument. This just keeps track of how many times the button had been pressed.

If you now close the TestControl’s designer window (you have to do this for the control to appear as active on the Toolbox) and build the solution (Build Solution from the Build menu), you’ll see the new control automatically added to the Windows section of the Toolbox. Add the control to Form1 just as you would with any other simple control. You now need to add some code in Form1 to handle the event raised by the TestControl’s ButtonClickEvent. It’s pretty easy:

Public Sub x(ByVal i As Integer) _
             Handles TestControl1.Button1ClickEvent
   MsgBox("you have clicked the button " + _
             i.ToString + " times")
End Sub

Now if you build the solution and press the button, you’ll get a message box indicating how many times you’ve pressed the user control’s button.


Once you’ve created the control, you can use it in much the same way as any other control in your programs

That’s how to get a user control to give information to you, but how about going the other way round – sending information to the user control? That’s easy too – just use a property:
Property mydata() As String
  Get
    mydata = TextBox1.Text
  End Get
  Set(ByVal Value As String)
    TextBox1.Text = Value
  End Set

This will get and set the text in the TestControl’s text box. Just use it as a standard property from the main form:

TestControl1.mydata = "hello"

Communications

Now to the real communications control. This month, I’ll stick to signaling communication events and setting the two control signals.

In order to do any work at all with a communications port, it has to be opened. Confusingly, this is done using the generic CreateFile API:

h = CreateFile(portname, GENERIC_READ Or GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0)

The portname argument must be the name of the port that you wish to connect to. The ‘official’ name is \\.\COM1 but the API will quite happily accept a simple COM1. As usual with API calls, you need to check for any errors. If successful, CreateFile will return a ‘handle’ or a reference to the file or device that it created or opened. If it failed, the handle is –1. To determine the cause of the error, you need to call GetLastError and throw an exception (next month, I’ll look at handling exceptions properly):

If h = -1 Then
   r = GetLastError()
   Throw New System.Exception("Port open failed")
End If

Now, in .NET, we can overload functions - that is, have multiple functions with the same name but with different ‘signatures’ (a fancy name for the name and type of arguments). The most typical thing that needs to be done is to open a port and set the speed:

Public Function Open(ByVal portnumber As Integer, _
               ByVal speed As String) As Integer
  Dim r As Integer, d As DCB
  Open(portnumber)

Setting the port’s speed is done by using a Device Control Block or DCB. There are dozens of possibilities for the port’s settings, but I’ve used just about the simplest – 8 bits per byte, no parity and one stop bit. In the DCB documentation, there’s a fairly horrible field, fBitFields, that allows all sorts of XON/XOFF control etc. For basic operation, you just need to set this to 1.

There’s one other peculiarity of the DCB – it needs to have the length of the structure, that is its own length, set. In C there’s a neat little operator called sizeof which does this. Unfortunately, it doesn’t exist in Visual Basic. But in .NET, there’s another way using the Marshal class – this deals with unmanaged memory – which does the business:

d.BaudRate = Int(speed)
d.ByteSize = 8
d.Parity = 0 ' no parity
d.StopBits = 0 ' one stop bit
d.fBitFields = 1 ' set binary mode
d.DCBlength = Marshal.SizeOf(d)

Finally, you use the SetCommState API to set the port’s speed and bit settings:

r = SetCommState(h, d)

So far, it’s all much the same as in Visual Basic 6 (and you can take the above code and pretty much graft it into a Visual Basic 6 program with little effort). But now we come to something you can’t do in Visual Basic 6: asynchronous events!

Waiting for something to turn up

What we want to do is to create an event when one of the control lines (DSR, CTS, RING or CD) changes. An API, SetCommMask sets the combination of events that we want to wait for:

m = EV_CTS Or EV_DSR Or EV_RING Or EV_RLSD
r = SetCommMask(h, m)

Then create a thread and start it:

t = New Thread( AddressOf x)
t.Start()

The key to handling events comes in this thread, x. This is what is looks like:

Private Sub x()
Dim r, m As Integer
Do
  r = WaitCommEvent(h, m, Nothing)
  RaiseEvent commEvent(m)
Loop
End Sub

In the thread, the code sits in a loop forever – there’s no exit clause. In the loop, the WaitCommEvent API will cause the thread to ‘block’ – the API will not return until the event condition specified in the mask, m, has been satisfied. This will occur when one of the control signals changes state. When that occurs, the API will return and the next Visual Basic statement, RaiseEvent, will be executed. The code will then call WaitCommEvent again, blocking until the next event occurs.


The thread started by the OpenWithEvents method will ‘block’ until a signal state changes, causing an event to occur

The event, commEvent, has been declared as:

Event commEvent(ByVal m As Integer)

This is handled in the main Form1 code by:

Private Sub commevent( ByVal m As Integer) Handles cc.commEvent
  MsgBox("m is " + m.ToString)
End Sub

You can try it out by pressing the ‘Test Comms’ button in the form and plugging a modem into COM1. You should see a small message box come up with a simple message indicating which event has occurred.


Here’s what happens when you disconnect a modem from COM1:
A a message box indicating which event occurred is displayed

The whole point about using a thread with a blocking API is that the rest of the program is free to do whatever it normally does – draw pictures, accept keyboard input and generally behave in an irresponsible fashion. It doesn’t need to worry about what’s going on in its child thread. Until of course the child thread indicates that something of interest has happened by raising an event.


RS232 Control Signals

Serial communication ports define a number of signals that can be used to find out about and control data flow...


This is how a standard RS232 plug is wired up. The colours indicate which signals are paired

RS232 is a standard, established some time ago, defining how two devices talk to one-another by sending a single stream of bits from one to the other. There are usually three ways of controlling this bit stream. The two devices can agree to use special characters, XON and XOFF, to signal that one or the other is ready (or not) to accept or transmit data.

The other two ways use one of two pairs of control signals that are available. These are DSR/DTR and RTS/CTS. DTR is an output signal from your PC to the other device – a modem say. The modem sets its own DTR signal which is read on your PC as the DSR signal. In other words, the signals are ‘crossed-over’. Normally, ‘asserting’ or setting DTR means that your PC is ready and willing to receive data and, conversely, if you monitor the DSR line, you can determine what the modem is up to: you are monitoring its DTR signal.

As well as the DSR/DTR signal, there’s the RTS/CTS signal which does pretty much the same thing. There are slight nuances of meaning between the two pairs, but they serve similar purposes.

Finally, there are two other read-only signals which may be useful. RI (or ring indicator) is set by the modem when a telephone attempts to connect. And CD (or Carrier Detect) is set by a modem when it has established a communications channel to a modem at the other end of the wire.


Next Month

I’ll add reading and writing capabilities to the control and in addition look at how to handle errors properly using the .NET error handling mechanism.

 

August 2005

 


Home | Archives | Contacts

Copyright © 2006 Dark Neon Ltd. :: not to be reproduced without permission