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
d.StopBits = 0
d.fBitFields = 1
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.
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.
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
|