Main Menu
Knowledgebase
Serial COM Port
Application.DoEvents
Communication
    Methods

Control.
    Invoke/BeginInvoke

Delegates
Download
Enumeration
Event Driven
    Sample Program

Event Keywords
Events and Messages
Forms and Controls
Interrupts
Invoke, BeginInvoke
    and EndInvoke

I/O Signals
Message Posting
Message Structure
Multitasking and
    Multithreading

Object Oriented
    Programming

Program Description
ReadMethod
RS-232
Serial Port Class
Serial Port Events
Serial Port Receive
Sleep
Standard Delegates
UART
Windows COM Port





Serial COM Port Communication

This page describes serial COM port communication by means of Visual Basic .NET
The page is updated August 5th 2010. It is only available in English.


 
 

Windows COM Port

Unfortunately Microsoft has never paid much attention to the serial port. In the Windows API it is just regarded as a file, and in the first version (1.1) of the .NET framework (managed code) there was no support for serial communication. Fortunately, a new namespace - System.IO.Ports - has been added in version 2.0, which has made things much easier although there are still some problems. For example, it is not possible to control the FIFO in the UART. It is also not possible to tell when the transmitter serial register is empty, so it is almost impossible to control the modem control signals and send a break condition, but worst of all, Microsoft has put an 8 bit wide buffer on top of the 11-bit receiver FIFO and therefore destroyed the possibility for a precise Break, 9th bit and error detection. Besides, many of the examples in the help files are directly misleading and unnecessary complicated. For example, it is recommended to use My.Computer.Ports.OpenSerialPort("COMx") to open a serial port, but if it is done that way, it is not possible to set many of the properties of the port like for example the length of the receive buffer and it is not possible to tell when a port is open (IsOpen).

In many developer forums there has been a lot of questions concerning serial port communication, but unless you are lucky enough to get in touch with the one, who has designed the serial port, it is usually very hard to get precise and helpful answers. For example, when we wanted to use very high speed communication (up to 921.6 Kbit/s), we only got the answer that it couldn't be done or we needed to write our own drivers! However, it is in fact possible to use System.IO.Ports even up to 921.6 Kbit/s if a UART with a 128 byte FIFO is used (16C850 or 16C950). As a service to others with the same problems as we have been through we have chosen to publish a small program, which is able to communicate through the serial port. The sample program is written in Visual Basic .NET (in the following just VB), because this language is as close as you get to our own suggestion for a simple and efficient programming language (see: Programming). In .NET there is no longer any performance difference between VB, C++ and C#.

 
 

WARNING! This description is based on VS 2005 and .NET 2.0. Unfortunately, SerialPort does not work in all versions of .Net.

To clarify the various .Net releases:

.Net 1.1 RTM (Release To Manufactoring) = No serial port support!

.Net 2.0 RTM = First version with serial port support.

.Net 3.0 RTM = .Net 2.0 RTM + new .Net 3.0 RTM assemblies.

.Net 3.5 RTM = .Net 2.0 SP1 + .Net 3.0 SP1 + new .Net 3.5 RTM assemblies.

.Net 3.5 SP1 = .Net 2.0 SP2 + .Net 3.0 SP2 + .Net 3.5 SP1 + new .Net 3.5 SP1 assemblies.

Only a few bug fixes were made to Serial Port in .Net 2.0 SP1:

  • Race condition between SerialPort.Close and event loop runner shutting down Serial IO WaitCommEvent enters high CPU and leaks memory when USB Serial Port removed (Internal Regression).

  • SerialPort.ReadExisting() returns incorrect characters.

And only one change were made to Serial Port in .Net 2.0 SP2:

  • UnauthorizedAccessException in SerialStream crashes application after disconnecting device from USB COM port. This should fix the issue of the unhandled exception when disconnecting a device from a USB COM port. However, there are reports indicating that the attempt to fix the bug doesn't work.

The big problem is .Net 3.5 RTM, which includes .Net 2.0 SP1. Almost nothing in SerialPort seems to work in that version! In fact, there seems to be so many errors that .Net 3.5 RTM may be regarded as completely useless for all serial port applications! For the moment the following problems have been reported:

  • It may crash if you access the modem control signals while the port is receiving.
  • It may crash if you try to close a port set to a wrong speed - even if you have an error handler for the ErrorReceived event.
  • You cannot use multiple ports.
  • In many cases, the DataReceived event does not fire.
  • If you try to run a 3.5 application generated under Vista on a XP PC, it may lock up the COM ports forever making them useless for all programs including Hyperterminal - even if you delete the application again or remove the 2.0/3.5 framework. It is necessary to repair or re-install Windows to return the ports to their funtionality prior to the loading of the application!

Microsoft thinks that the many problems with serial port in .Net 3.5 RTM may be coursed by other, unrelated(!), bug fixes that went into .Net 2.0 SP1. Most of these problems seems to be fixed in .Net 3.5 SP1, but the problem is that nobody knows what coursed all the problems so nobody knows whether the problems are fixed or not! For the moment it cannot be guaranteed that 3.5 SP1 works as well as 2.0. VS 2008 allow you to select the wanted .NET version when you build your application. This is done in Advanced Compilation Options. Be sure to select either 2.0 or 3.5 SP1!


 
 

Communication
Methods

Basically, there are two ways to do serial communication - polled and event driven.

In the polled mode, the transmitter sends one or more bytes, which are typically the address of the unit to be polled. The polled unit then returns the answer, which is then displayed. If the answer is not received within a given amount of time, an error message should be displayed. If the poll and the answer are short and the polled unit answers immediately, a poll program does not necessary need multithreading as everything is done in a precise order. The polled unit does not answer before the poll has been transmitted and a new poll is not initiated before an answer is received or a timeout situation occurs. The program may therefore consist of a single subroutine with a loop, which transmits the poll (unit address), waits for the answer or a timeout, converts any non-text answer to text and then display the text. The polled mode is very simple to program, but since both the poll and the answer depends on the application it is not possible to make a general-purpose program. If you need this mode, you cannot use our program directly, but it may serve as an inspiration - especially in situations where multithreading is desiable to awoid blocking the UI thread.

In the event driven mode, the transmitter and the receiver are completely asynchronous, that is, there is no connection between the transmitter and the receiver. The receiver just displays everything, which is received. This is the mode our program is intended for, but it may also be used in polled mode if the transmitter is looped back to the receiver so that the poll is also received, and the transmission uses a protocol, which makes it possible to tell the start of the telegram (distinguish between poll and answer).


 
 

Event Driven
Sample Program

When the program is loaded, a list of the available COM ports is generated and put into a ComboBox. One of these possibilities must be selected before any communication can take place. At any time it is possible to change the COM port selection and the Baud rate. The Transmitter TextBox accept any mix of ASCII characters embedded in quotation marks and hexadecimal numbers with an even number of digits like for example """Test""" 78 90 "UU" FF76. A Carriage Return (CR or Enter) is specified as its hexadecimal value 0D and a Line Feed (LF) as 0A. Note that to transmit " in an ASCII string it is necessary to write "" - exactly as in Visual Basic. In hexadecimal mode, all spaces, CR's and LF's are ignored. In case of hexadecimal numbers with four or more digits, the numbers are transmitted with the big-endian model, that is, with the most significant part first. The Send button transmits the content of the Transmitter TextBox and the Break button generates a Break condition. The receiver TextBox always writes hexadecimal characters except for a detected Break condition, which is written as "Break". Note that because both the transmitter and the receiver regard all numbers as hexadecimal, no hex specifier is used! 63 means 63 hex (99 decimal) - not 63 decimal! There is no decimal to hex conversion in the program.

The maximum telegram length is limited to 2048 bytes, but it is very easy to change this if longer telegrams are needed.

The sample program is very simple and the code speaks very much for itself, but because we know that it may still seem overwhelming to absolute beginners we will describe it step by step in the following together with a detailed description of the used .NET methods and properties. Note however that some basic knowledge about subroutines, functions, data types and declarations etc. are needed to understand the description.

If you are a beginner, it may easily take you days or weeks to go through and understand this totorial, but don't step to a new chapter before you understand the previous one! Take it easy, step by step. The user interface (UI) of .Net is entierly event driven and serial port uses multithreading. Unless you understand how this works, you will never be a good .Net programmer and will never be able to make reliable programs. No matter what .Net programming you need to do, you will need most of the knowledge in this totorial so forget your serial port problems for a while (the reason why you came here) and get the necessary background knowledge first. Then your serial port application will be a piece of cake to program. SerialPort is really not that difficult. For example, this is all you need to read an ASCII string and append it to the text in the Received textbox. The difficult part is to understand what you are doing!

   Imports System.IO.Ports

   Public Class MyFirstCOMProgram

       Public Delegate Sub StringSubPointer(ByVal Buffer As String)
       Dim WithEvents COMPort As New SerialPort

       Private Sub Receiver(ByVal sender As Object, _
           ByVal e As SerialDataReceivedEventArgs) Handles COMPort.DataReceived
           Me.BeginInvoke(New StringSubPointer(AddressOf Display), COMPort.ReadLine)
       End Sub

       Private Sub Display(ByVal Buffer As String)
           Received.AppendText(Buffer)
       End Sub

       ' Initialization somewhere in the program where you open the port.

           COMPort.PortName = "COM1"
           COMPort.BaudRate = 19200
           COMPort.ReadTimeout = 2000
         ' COMPort.NewLine = Chr(xx) in case the telegram is not terminated with LF.
           Try
               COMPort.Open()
           Catch ex As Exception
               MsgBox(ex.Message)
           End Try

       Private Sub MyFormClosing(ByVal sender As Object, _
           ByVal e As  ComponentModel.CancelEventArgs) Handles MyBase.Closing
           If COMPort.IsOpen Then COMPort.Close()
       End Sub

   End Class
   



 
 

Enumeration

In .NET it is possible to assign values to names with the structure GroupName.Member like this:

   Public Enum MyColors
       MistyRose = &HE1E4FF&
       SlateGray = &H908070&
       DodgerBlue = &HFF901E&
       DeepSkyBlue = &HFFBF00&
       SpringGreen = &H7FFF00&
       ForestGreen = &H228B22&
       Goldenrod = &H20A5DA&
       Firebrick = &H2222B2&
   End Enum
   

This is called an enumeration. When the compiler sees for example MyColors.MistyRose, it replaces it with the RGB value &HE1E4FF (Red = &HE1, Green = &HE4, Blue = &HFF). Note that you can only assign values like integers - not other enumerations. You can therefore not assign something like this:

       MyRed = colors.Red
   

You can also use enumeration to get the value corresponding to a text string although it is more complicated. For example, you can load a ComboBox (ComboBox1) with all the names in the enumeration list like this:

   For Each s As String In [Enum].GetNames(GetType(MyColors))
       ComboBox1.Items.Add(s)
   Next
   

When you select an item from the comboBox, you can convert to the equivalent RGB value like this:

   Dim RGBValue As Integer = [Enum].Parse(GetType(MyColors), ComboBox1.Text)
   

Note that in both cases, you use GetType(MyColors) to specify the group name.

Constructions like this are used in the serial port program for example to select the parity and the software flow control. It is also very useful in case of a Select-Case construction.


 
 

Object Oriented
Programming

.NET is based on the so-called object oriented programming (OOP). If you want to build a machine, you start by making a drawing to define how the machine shall look like and what it is able to do. You can then use this drawing to build many machines of the same type. OOP works the same way. Each machine type is called a class. When you make the "drawing", it is called to define the class, and when you build the machine, it is called to declare the object. Note that the "drawing" is the class and the actual machine is the object. Also note the difference between definition and declaration. Each new machine you build from the same drawings is called an instance of that class.

Everything what the machine is able to do is called methods and all the parameters you set up on the machine to be able to define how the product shall look like are called properties. For example, a machine may be able to drill holes and cut. This is the methods. The size and position of the holes are selected by means of the properties. In fact, everything is done by means of methods. When you set or read a property, you actually call some hidden methods to do the job. For example, myControl.BackColor = Colors.Green is really a shorthand for myControl.Set_BackColor(Colors.Green). You cannot access these methods directly, but what is important to note is that setting a property is equivalent to calling a method (Set_BackColor). This is important in case of cross thread calls (described later). The argument for the method (Colors.Green) is an example of an enumeration.

If you want to build a new machine similar - but not identical - to an existing one, you don't need to start all over by making a complete new set of drawings. This would also confuse things because it is difficult to tell what the difference between the two models is, and you have to change both set of drawings if a common detail is changed. Instead, you can just take an existing drawing and then specify the changes or additions you want to make. This is called to inherit a class from another class, and the new class is said to be derived from the old one.

All data types are actually classes with a predefined definition. For example, a byte is defined as a storage location big enough to hold 8 bits of data. Some of the more complex data types like arrays and strings have methods. For example, you can get the length of a sting by calling the Length method like this:

   LengthOfString = YourString.Length
   

VB use the keyword "As" to declare the class, that is, to specify which "drawing" to use, and it uses the keyword "New" to actually build the object.

   Dim MyControl As Control
   

specifies that MyControl is a member of the control class, but it does not build the object and therefore does not consume any memory! It is basically an information to the compiler. If you try to use MyControl before the object is build, you will get a NullReference exception.

   MyControl = New Control
   

builds the object MyControl. Note that MyControl has to be declared by means of an "As" statement before you can build the object.

If the class has a constructor method called "New" to initialize the object, this method is called when the object is build. This is used to load a new object with initialization data like this:

   TextString = New String("Hello World!")
   

In this case, "Hello World!" is passed to the constructor. The initialization data must be enclosed in parentheses following the class name. However, this is the same syntax as a function call, so in this case, the keyword "New" is also used to be able to destinguish between the returned value from a function and the build of a new object. Without this keyword, it would be impossible to tell whether String("Hello World!") is a function (String) with "Hello World!" as argument or a declaration of a new instance of the String class with "Hello World!" as (initialization) value.

A constructor is always a subroutine and it is possible to have more constructors to make the input and initialization more flexible. If you for example have a class - MyClass, which may or may not require a string as input, the constructors may look like this:

   Public Class MyClass

       Public Sub New()            ' Executed in case of no input
           Me.New("Hello World!")  ' Call the other contructor with a default input
       End Sub

       Public Sub New(ByVal TextInput As String)
           ...                     ' Make any further initialization common to the two constructors
       End Sub

       ....

   End Class
   

In this case, the default input "Hello World!" is used if no input is specified when the object is build. In the chapter "Delegates" of this tutorial, you will see another example of a constructor.

If you wish to build an object at the time of declaration, it is possible to combine the "As" and "New" statements like this:

   Dim MyControl As New Control
   

If you for example want to make a new instance of SerialPort, you can for example do it like this:

   Dim COMPort As New SerialPort()             ' Use default initialization
   Dim COMPort As New SerialPort("COM3", 19200, Parity.Even, 7, StopBits.Two)

   Dim WithEvents COMPort As New SerialPort()  ' Make it possible to fire events
   Dim WithEvents COMPort As New SerialPort("COM3", 19200, Parity.Even, 7, StopBits.Two)
   

Because of multiple constructors, you can choose to use more or less of the default inputs "COM1", 9600 bit/s, No parity, 8 data bits and 1 stop bit, but only in that order. Note the use of enumeration to specify the parity and the number of stop bits. The keyword "WithEvents" makes it possible for subroutines to subscribe to events from this object (COMPort) by means of the keyword "Handles". This is described later in the chapter "Event Keywords".

For simple VB data types you don't need the keyword "New". The object is build when you assign a value to it, but not before! For example:

   Dim TextString As String      ' Declare TextString as a member of the string class
   Dim MyInteger As Integer      ' Declare MyInteger as an integer
   ...
   TextString = "Hello World!"   ' Build the string object and load it with "Hello World!"
   MyInteger = 361               ' Build the storage location and load it with 361
   

As with the "New" keyword it is possible to combine the two statements if you wish to build the object at the time of declaration and save some typing.

   Dim TextString As String = "Hello World!"
   Dim MyInteger As Integer = 361
   

WARNING! In .Net, all strings are immutable, meaning you cannot change its length or contents once it is declared. Therefore, all string manipulations has to be done by making a new instance of the String class, copying the string(s) and then abandon the previous string. This is a very time consuming procedure, which also leads to a serious fragmentation of the memory. Therefore, constructions like the one below should be avoided - especially for repetitive calls - even though they are possible:

   NewString = OldString1 & OldString2 & vbCrLf
   

Instead, it is recommended to use a Char array to build and expand the string and then at last convert this to a string like this (some declarations and initializations not shown):

   Dim ReceiverArray(2047) As Char
   ...
   ReceiverArray(I) = ReceivedChar
   I = I + 1
   ...
   Dim ReceivedString As New String(ReceiverArray, Offset, Length)
   

You can also use the StringBuilder class. The immutable strings is actually one of the less clever things of .Net. A string declaration could easily have a maximum length like arrays, but unfortunately this is not the way it is done.


 
 

Forms and Controls

In .NET, all applications and services are build by means of one or more Forms. A form may be visible in case of applications or invisible in case of services. Each visible form represents a top-level window on the screen (TopLevel property = true). When working in the VB IDE (Integrated Development Environment), a form is the "designer" that is used to design the UI.

Controls are used in the designer to create the look of the UI. A control is an object that has a predefined appearance and behavior. For example, a Button control looks and behaves like a push button. Each control in VB has its own purpose. For example, a TextBox control is used for entering and showing text, while a PictureBox control is used for displaying pictures. When designing your UI, you drag controls from the Toolbox, drop them onto a form, and then position them and resize them to create the desired look. You can further change the name, appearance and behavior of forms and controls by setting properties in the Properties window. For example, the ShowInTaskbar property of a form determines if the form will appear in the Windows taskbar when the program is running. You can also make your own controls.

The object Inheritance hierarchy of forms and controls is shown below:

   System.Object 
       System.MarshalByRefObject 
           System.ComponentModel.Component 
               System.Windows.Forms.Control 
                   System.Windows.Forms.ScrollableControl 
                       System.Windows.Forms.ContainerControl 
                           System.Windows.Forms.Form
                               (Derived Classes)
   

You can forget all classes except for controls and forms, but it is important to notice that forms are actually just an advanced control, so basically everything is just windows on top of windows or even more correct - controls on top of controls. In the following "window" means form or control and a "form" is a top-level window.

Because all controls are derived from System.Windows.Forms.Control, they all have at least the same basic properties, methods and events. There are numerous, but a few of these are very commonly used and may be used and/or described in this tutorial:

Public Properties:

BackColor
InvokeRequired
Visible

Public methods:

BeginInvoke
CreateGraphics
Dispose
EndInvoke
Hide
Invalidate
Invoke
Refresh

Protected methods:

OnClick
OnMouseClick
OnPaint
OnPaintBackground
WndProc

Public Events:

Click
MouseClick
Paint


 
 

Multitasking and
Multithreading

Windows is capable of keeping many programs in memory at once and give each of them an opportunity to run. This is called multitasking. Windows NT, 2000 and XP runs each program in a separate process. This is an artificial division that gives the programs mutual isolation so that no problem can indirectly affect the operation of other programs. The process is started when the program starts and exists for as long as the program is running. Each process manifest itselves as an application or a service. The difference between applications and services is that services don't usually have a user interface (in the following just UI), whereas applications do.

Windows is able to run more parts (threads) of the various processes virtually simultaneously. This is called multithreading. A thread is in effect an execution pointer, which allows Windows to keep track of which line of your program is running at any one time. This pointer starts at the top of the program and moves through each line, branching and looping when it comes across decisions and loops and, at a time when the program is no longer needed, the pointer steps outside of the program code and the program is effectively stopped.

Application software uses more threads primarily to deliver a better user experience, because it allows some programs like spell checkers, print spoolers etc. to run in the background. Service software uses more threads to deliver scalability and improve the service offered, for example to be able to handle more channels in parallel.

Windows uses timeslicing to switch between the various threads. Because the execution of a thread is interrupted due to an external factor such as time-slicing, this is called preemptive multithreading. The run-time each thread gets before a new thread is selected is not fixed, but depends on the priority and varies dynamically to obtain the best possible performance. It is usually either one to two time slices where each time slice is approximately 10 milliseconds on a uniprocessor system and 15 milliseconds on a multiprocessor/multicore system. The run-time for each thread does not depend on the process, so a process with many threads will get more execution time than a process with only a few threads. To keep the UI responsive, all UI threads (described below) have higher priority than for example thread pool threads and most background threads. A high priority thread always preempts a lower priority thread, but to prevent low priority threads from getting starved and solve nasty priority-inversion problems, the scheduler continuously adjusts the actual priority from the programmed priority.

The advantage of timeslicing is that the processes don't have to actively participate in this process and that it is not possible for one process to block the operation of other processes. The disadvantage is a fairly low efficiency compared to cooperative multitasking/multithreading where the programmer selects the ideal switching points. For example, a timeslice system may waist a lot of time on threads, which cannot utilize the full timeslot, but even worse - there is no guarantee that a subroutine or function has finished the use of some data before another part of the program can change these data. Therefore, a subroutine of function cannot just be called with a reference to the data (ByRef), but must be called with its own copy of the data (ByVal). All this data copying takes a lot of time and increases the necessary stack size. It may even lead to a serious fragmentation of the memory if the data cannot be located on the stack. Therefore, preemptive multitasking is usually used in the PC world where there is a lot of memory and a very fast CPU and where programs from many different vendors must work together, and cooperative multitasking is used in the embedded world where speed and efficiency is a primary concern.

No process can write directly to the data area of another process, but threads within the same process may share the same global variables. However, more threads should of course not write to the same data area at the same time as this may create unpredictable results. In a time-sliced system there is also a risk that a method called from one thread does not finish its job before it is called again from another thread. To prevent this, .NET (unlike previous versions of VB) simply do not allow such a cross thread call except for four so-called thread safe methods - Invoke, BeginInvoke, EndInvoke and CreateGraphics - plus some hiden methods to set some properties. A piece of code is thread-safe if it functions correctly during simultaneous execution by multiple threads. In particular, it must satisfy the need for multiple threads to access the same shared data, and the need for a shared piece of data to be accessed by only one thread at any given time. Because shared data must be located somewhere and everything is running on a thread, you may also say that a method is thread safe if it is able to safely use data area (shared data) of another thread.

It is not defined anywhere which properties you are allowed to set from another thread, but it is probably the ones, which may be contained in a single 32-bit value (like BackColor), since there is now way such a property can be half updated on a 32-bit system. One of the cardinal rules of Windows GUI programming is that only the thread that created a control like for example a TextBox can access its methods or should modify its contents except for simple properties like for example the background color etc. Try doing it from any other thread and you'll get unpredictable behavior ranging from deadlock, to exceptions to a half updated UI.

It is very important to notice that all controls and forms have the four thread safe methods (see last chapter), so if you for example want to call BeginInvoke to invoke a method on the UI thread, you can use any of your controls on the UI thread like for example TextBox1.BeginInvoke(...) or Button2.BeginInvoke(...) or you can use your form and simply write Me.BeginInvoke(...).

Every application and service has one primary thread - Main. This thread serves as the main process thread through the application or service. It's this thread that calls the Main method, which is the first code to run when your application or service has been loaded. Main serves as the starting point and overall control for your application or service. A file that runs on its own (usually with extension .exe) must contain a Main method. For a console application, you can write Main to accept user input and process them. For a GUI (Graphical User Interface) application (Windows Forms) like VB, which is entierly event driven, the compiler generates the Main method automatically. All windows (forms or controls) belong to the thread on which they are created. This thread is called the UI thread and it is usually the main thread.

Threads are a limited resource. They take approximately 200,000 cycles to create and about 100,000 cycles to destroy. By default they reserve 1 megabyte of virtual memory for its stack and use 2,000-8,000 cycles for each context switch. It is possible to make and control threads yourself, but usually it is neither necessary nor appropriate due to the huge use of resources. Windows has a pool of 25 general useable threads called thread pool threads, which may be used instead for background jobs. The pool is generated the first time it is used. You may regard this pool as a company with 25 workers, which you can hire to do a job for you. If you request a thread from this pool and no thread is available, Windows generates a new pool thread up to a total of 100 threads. Requests beyond that limit is queued until a thread becomes available. The extra threads are destroyed as soon as they are no longer needed until the number of threads in the pool reaches the minimum level of 25. Because it takes approximately 300,000 cycles to create and destroy a thread, it is obvious that you should try to avoid that more thread pool threads than 25 are needed.


 
 

Sleep

It is possible to stop a thread temporary by means of System.Threading.Thread.Sleep(time). The time is specified in milliseconds, but the accuracy is much lower. If you for example try to make a 1 mS delay by means of Sleep(1), the thread is temporary suspended. 1 mS after it is put on the active queue again, but in the meantime another thread may be running and there may be more threads in the queue - even with higher priority, so it can easily take 50 mS or longer before the thread starts running again. Even if there are no waiting threads, it may take up to 15 mS - the next time slice tick - before the thread runs again. Therefore, you won't be sure when you get control back, so Sleep cannot be used for accurate timing.

Sleep(0) can be useful to tell the system that you want to forfeit the rest of the thread's timeslice and let another, waiting, thread run. If there are no other threads waiting to run you still relinquish your timeslice. This is similar to the typical Release call in a cooperative multitasking system. Sleep(0) may be very useful in a game loop. It gives you the maximum possible frame rate that still doesn't completely hog the processor, allowing the system to stay responsive to for example Alt+Tab and ding-dong for email deliveries. However, you'll burn 100% CPU time, which many PC's are not designed for. Sleep(1) is similar, but keeps the CPU cool. Sleep(1) is very useful in soft real-time systems. You'll need this when you have to rely on polling to discover anything happening in the system. This is very common with imperfect drivers, which can't generate an event.

If Sleep is called from the UI thread, the user interface will be completely dead for the specified amount of time. Therefore, calling Sleep() from the UI thread is not recommended except to solve "don't know enough about the state of the object" problems. For example, the SerialPort class has a nasty problem where you can't open a port too quickly after closing it. There is a background thread, which needs to exit before you can successfully open the port again. This is a flaw in the class design. Sleep() can solve this, but the solution is not perfect. The exact amount of time you need to be sure the thread exits (the amount of time needed before the background thread gets scheduled again) depends on system load, but because all time consuming, high priority UI jobs are temporary suspended, Sleep(200) is usually enough although Sleep(500) is recommended for high reliability applications. If you don't sleep the UI thread, but for example use a timer instead, you must use a considerably longer time to ensure that the background thread gets enough time to close down - for example 2 seconds!


 
 

Interrupts

It is possible to interrupt a thread by means of a so-called interrupt. When you make any action such as to click a mouse button, move the mouse, type on the keyboard, resize or move a window etc., or if you receive something on a communication port or a hardware timer runs out or generates a timer tick, the hardware raises an interrupt signal. This start a long, rather complicated chain of actions. If you are a beginner, just skip this chapter. It will just confuse you.

  1. The interrupt signal is received in an integrated circuit called the interrupt controller. The controller gives each interrupt request (IRQ) its own priority so that the interrupt with the highest priority can be executed first in case there are more interrupts. Each interrupt has an interrupt vector, which points to the interrupt routine, which shall be executed when the interrupt becomes the one with the highest priority. On most PC's there are only 15 hardware interrupts, which is by far not enough for all devices, so it may be necessary for many devices to share the same interrupt. Therefore, the interrupt vector does not point directly to the interrupt service routine (ISR). Instead, each device has an interrupt object with a dispatch code. The interrupt vector points to this code. The dispatch code is generated when a device driver is loaded. If more device drivers want to use the same hardware interrupt, that is, same interrupt vector, the interrupt objects are linked together in a chain.

    Windows does not depend entirely on the hardware interrupt priority. It has its own priority system with 31 interrupt request levels (IRQL). Unlike the interrupt controller, where IRQ 0 has the highest priority, Windows priorities are inverted so that IRQL 31 has the highest priority. IRQL 27-31 is used for very high priority interrupts like power failure (IRQL 30) and clock (IRQL 28). IRQL 3-26 is used for device drivers mapped as 27 - IRQ = IRQL. IRQL 1-2 are software interrupts where IRQL 2 is called Dispatch or DPC level and IRQL 1 is APC level. Windows uses IRQL's for implementing a priority mechanism similar to the one in the interrupt controller. If for example IRQL is set to 8, all interrupts with an IRQL of 8 or below are disabled. If IRQL is 0, all interrupts are enabled. Each interrupt object also contains information about what IRQL to use for the ISR.

  2. The dispatch code in the interrupt object calls KiInterruptDispatch if there is only one interrupt object for this vector. If there are more objects it calls KiChainedDispatch.

    1. KiInterruptDispatch or KiChainedDispatch raises the IRQL to the level specified in the interrupt object to block for all other interrupts with the same or lower priority.

    2. KiInterruptDispatch or KiChainedDispatch calls the ISR.

      1. The ISR reads short information like status etc. from the device.

      2. The ISR acknowledge the interrupt.

      3. The ISR is not allowed to do any time consuming actions. Therefore, each device driver has a Deferred Procedure Call (DPC) object, which primary consist of a pointer to a routine, which can perform all further I/O. The ISR calls KeInsertQueueDpc, which puts the DPC object on a DPC queue (one queue per processor core in case of a multi processor system) and fires a software interrupt at Dispatch level (IRQL 2).

    3. KiInterruptDispatch or KiChainedDispatch lowers the IRQL to the one before the interrupt to enable pending interrupts.

  3. If the IRQL is below the dispatch level, the DPC queue will be drained by the dispatch level interrupt routine invoked from the ISR.

    1. The DCP routine performs all I/O operations.

    2. The DCP routine calls IoCompleteRequest to inform the I/O manager that it has finished the I/O operations.

      1. The I/O manager queues a kernel mode Asynchronous Procedure Call (APC) I/O completion routine on the APC queue of the caller's thread.

        APC routines are used to execute some code asynchronous in the context (address space) of a particular thread. Therefore each thread has its own APC queues - one for kernel mode and one for user mode. When a thread is scheduled (runs), an APC interrupt occurs (IRQL 1) if there are any routines in the queues. This will course execution of the APC routines in the right address space. You may say that APC routines are used to delay some code until the right thread and address space has been selected and the IRQL is reduced to passive level (IRQL 0).

  4. The APC routine writes any I/O data and the return state to the data area (address space) of the thread (it runs on that thread).

  5. The APC routine releases all threads, which have been waiting for the I/O operation to complete.

  6. If any user mode APC's are specified, these are queued on the user mode APC queue. By means of APC routines, one thread can execute code in the address space of another thread - even a thread from another process. This is used to call a so-called callback method when for example an I/O operation has finished or a timer runs out. A callback method is not executed immediately on the thread from which it is called, but delayed and executed on the thread, which shall receive the result.

Thread switching is also done by means of a DPC routine. If the clock ISR (IRQL 28) has determined that a thread has reached its maximum execution time (time slice), it also calls KeInsertQueueDpc, to put the thread switching routine on the DPC queue. All thread synchronization is therefore done at dispatch level (IRQL 2), so no other thread related jobs must take place at this or higher levels. If a thread has been waiting for a timer tick, the ISR routine on IRQL 28 is therefore not allowed to release the thread directly. Instead it must post an APC routine, which can do the job.

In practice, interrupt handling is even more complicated than described here - especially on a multi processor system, but these are the basic principles on a 32-bit system with all minor details omitted. On a 64-bit system, the interrupt system is similar, but with some modifications.

The interrupt latency, that is, the time it takes from a hardware interrupt occur until the DPC routine is finished (has performed its I/O), is usually below 1 mS. This is important when working with serial port because you must have a hardware FIFO buffer (First In, First Out), which can hold all characters you receive until the DPC routine in the driver has transfered these data to a software buffer in the driver. This software buffer must then be big enough to hold the characters until they can be further processed and/or displayed. As this may easily take 100 mS, the software buffer in the driver should be at least 100 times as big as the hardware buffer.


 
 

Events and Messages

The interrupt system (DPC routine) is not allowed to perform very time consuming or thread related operations, so all further processing must be done by non-interrupt routines. There are two ways to do this.

  • Each event can be executed on its own thread. This is usually done by means of a background thread, which waits for one or more events. It can then grap a thread from the thread pool and use this for further processing of the various events so that the background thread immediately gets ready for the next event. This is the method used in SerialPort.

  • More events can be executed on the same thread. This is the method used by the UI. All UI related actions gets translated to a message. Each message has a name, which starts with WM_ (Windows Message) like WM_CLICK, and it has an associated method, which shall be called when the message is handled. This method has a similar name except that WM_ is replaced with "On" like OnClick. Very urgent messages like mouse cursor movements (WM_SETCURSOR) and change of active window (WM_ACTIVATE and WM_SETFOCUS) are handled immediately, but most messages are linked together with other messages to form a message queue. It is possible to post up to 10,000 messages to one queue. With the exception of the WM_PAINT message, the WM_TIMER message, and the WM_QUIT message, the system always posts messages at the top of a message queue by means of a pointer, which points to the previous message. This ensures that a window receives its input messages in the proper first in, first out (FIFO) sequence. The WM_PAINT message, the WM_TIMER message, and the WM_QUIT message, however, are kept in the queue and are only handled when the queue contains no other messages. In addition, multiple WM_PAINT messages for the same window are combined into a single WM_PAINT message, consolidating all invalid parts of the client area into a single area. Combining WM_PAINT messages reduces the number of times a window must redraw the contents of its client area.

    The routine that reads from the message queue is called the message pump. It's basically an (almost) infinite loop that waits for someone to post a message to the queue by means of GetMessage as shown below:

       While GetMessage(Msg, 0, 0, 0)
          TranslateMessage(Msg)
          DispatchMessage(Msg)
       End While
          

    Once someone post a message, GetMessage returns (with the value true). The loop then performs any message translations by means of TranslateMessage. For example, the keyboard sends raw, virtual-key messages like WM_KEYDOWN and WM_KEYUP. These virtual-key messages contain a code that identifies which key was pressed or released, but not its character value, which might depend on the state of the Shift or Alt key or the character Language. TranslateMessage converts the virtual-key codes into a more user friendly character message (WM_CHAR). At last, the message pump calls DispatchMessage, which calls the message handling procedure - WndProc - of the control (window) specified in the message. Note that in case of one of the four thread safe methods - Invoke, BeginInvoke, EndInvoke and CreateGraphics, it is WhdProc of the control you have used in the call, which will receive the message - for example Button2.WndProc in case of Button2.BeginInvoke - even though Button2 may have absolutely nothing to do with the message!

    WndProc is basically a Select-case construction, which calls the method associated with the message like this:

       Protected Overridable Sub WndProc(ByRef m As System.Windows.Forms.Message)
           Select Case m.msg
               Case WM_CLICK
                   OnClick(m.lParam)
               Case WM_ERASEBKGND               ' Paint background
                   OnPaintBackground(m.lParam)
               Case WM_PAINT                    ' Paint foreground
                   OnPaint(m.lParam)
               Case ...
    
               Case WindowsForms20_ThreadCallbackMessage
                   Me.InvokeMarshaledCallbacks  ' Empty the ThreadCallbackList of the control
    
               Case WM_CLOSE 
                   RaiseEvent Closing()         ' Old obsolete closing event
                   RaiseEvent FormClosing()     ' New 2.0 form closing event
                   If Not Cancel Then
                       DestroyWindow(m.hWnd)    ' Close specified window/form
                   End If
               Case WM_DESTROY 
                   ...                          ' Close all windows/forms
                   PostQuitMessage(0)     ' Stop the message pump and terminate the application
           End Select
       End Sub
    
       Protected Overridable Sub OnClick(ByVal e As System.Windows.Forms.BulletedListEventArgs)
           ...
           RaiseEvent Click(e)  ' Execute all event handlers for the Click event
       End Sub
    
       Protected Overridable Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
           ...                  ' Do whatever painting it has to do
           RaiseEvent Paint(e)  ' Execute all event handlers for the Paint event
       End Sub
          

    The PaintEventArgs specifies the Graphics to use to paint the control and the Rectangle, which needs to be (re)painted. This is used in both OnPaint and OnPaintBackground.

    The 20 in WindowsForms20_ThreadCallbackMessage depends on the version - 11 for .NET 1.1 and 20 for version 2.0. WindowsForms20_ThreadCallbackMessage is described in more details in the control.Invoke/BeginInvoke chapter, but it should be noted that these kind of messages have higher priority than input messages like WM_KEYUP and WM_KEYDOWN. This means that if a program keeps sending such messages to the message queue, the window will not react on e.g. keyboard input! Therefore it is very important not to overload the message queue with these kind of messages!

    In .NET 2.0, the form closing event, which has only CancelEventArgs, is obsolete and replaced with the FormClosing event, which inherits from Closing, but also has information about the reason why the form is closed (CloseReason property). Note that both events are raised before the form is closed so that it is possible to cancel the operation (instance.Cancel = true).

    Note that very urgent messages like WM_SETCURSOR are send directly to WndProc bypassing the message queue.

    When the associated method like OnClick or OnPaint has done whatever it has to do, it must call any other methods, which shall also be executed when this event occurs. These methods are called event handlers, and the method, which executes the event handlers, is RaiseEvent. It is a very confusing name because it makes you believe that it raises a new event like raising a flag or an interrupt. It doesn't. It just execute some event handlers from a list, but it is a convenient way of describing things. You write some event handlers and when you want to execute these, you just "raise the event". The event has a name similar to the message and the associated method except that no prefix is used like for example just Click. When the method and any event handler(s) have finished their jobs, DispatchMessage returns and the loop calls GetMessage again to wait for the next message. When GetMessage receives a WM_QUIT message (posted with PostQuitMessage by means of WM_DESTROY) it returns with the value false and the While loop exits.

    Let us take an example. If the user moves or resizes a window, some part of the window - a rectangle, a region or maybe the whole window - needs to be redrawn. The method, which has moved or resized the windows, tells which part it is by calling Invalidate. However, there may be other UI events, which has occured before the window has been changed and also needs to be executed, so the redraw is not done immediately. Instead, Invalidate sends the messages WM_ERASEBKGND and WM_PAINT with the necessary PaintEventArgs to the message queue. When WM_ERASEBKGND reaches the bottom of the queue, the background is repainted by means of the OnPaintBackground method and when WM_PAINT reaches the buttom and there are no other messages in the queue except for WM_QUIT (as described previously, WM_PAINT, WM_TIMER and WM_QUIT have lower priority than all other messages), the OnPaint method is executed and will do whatever foreground painting it needs to do. Note that the time difference between background paint and foreground paint will course flicker if you don't use double buffering - especially because of the low priority of WM_PAINT. When WM_PAINT is finished, it will raise the Paint event, that is, execute all handlers for this event. If you for example has created graphics with CreateGraphics, this graphics may need to be redrawn so you must make a handler for the Paint event, which can do this.

    The OnPaint method and any other methods associated with a message are overridable so that they may be changed if you derive a class from another. If you for example want to add something to the OnPaint method of the class ChildClass, which is derived from ParentClass, you can do it like this:

       Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
           ....                          ' The code you want to add
           MyBase.OnPaint(e)             ' Call the original OnPaint method to do the rest and at last raise the Paint event
       End Sub
          

    This is the preferred method rather than making some routines in ChildClass, which subscribe to the Paint event of ParentClass. All applications or services, which use ChildClass, can then subscribe to the Paint event of this class if they have any graphics to redraw in case the window is invalidated.

    Because the message pump is used to handle events for the GUI, it runs on the Main thread, on which all GUI objects are created. A typical GUI application, after doing some initialization in the Main method, will then start running the message pump. Because the message pump is an infinite loop it will run until the application closes, so the Main thread can't do anything else once the message pump starts.

    The System.Windows.Forms.Application.Run() method takes care of message pumping. If no form is specified, it begins running a standard application message loop on the current thread (usually the UI-thread) for the application or service. If a form is specified, it also makes the specified form visible like:

       Shared Sub Main()                 ' Called from the primary thread
           Application.EnableVisualStyles()
           Application.SetCompatibleTextRenderingDefault(false)
           Application.Run(New Form1())  ' Start the application and make Form1 visible
       End Sub
          

    In case of an application, a form is usually specified or else there is no UI the user can click, but it is also possible to show the form later by means of NameOfForm.Show if an event to do this is available. If you want to hide a form, you can call NameOfForm.Hide. This will set the Visible property to false. If you want to close a form, you can call NameOfForm.Close. This will post a WM_CLOSE message, which will close the form.

    When you want to terminate your application for example by selecting Exit from the File menu, clicking on the close button (the small X button in the upper right corner of your window), or presses Alt+F4, you must call Application.Exit(). This call sends a WM_DESTROY message to the message queue, which then closes all windows/forms and at last post a WM_QUIT message, which stops the message pump and exits the application as explained before.

    Each application can have many UI-threads, which again can have many windows, but the thread that creates a window must also be the thread that handles all messages send to that window. That means that any thread that creates GUI objects (= Windows) must have a message pump so that it can receive the messages.

    All the controls in a single window must be created on that windows UI thread, but if you have multiple top-level windows, you can have multiple UI threads - as many as you have windows if you really want. More UI threads in a application is for example used by Internet Explorer and when a MessageBox pops up. It has the advantage that if one window is blocked the others may still be functional.

All methods and event handlers runs on the same thread as the one from which they are called - either directly, by means of a delegate (described later) or by means of a raised event, which is actually the same as a delegate call! Because the message pump runs on the UI thread, the methods it calls will do the same, and if these methods raise events as the OnPaint method, the event handlers will also run on the UI thread. However, if an event handler is activated by means of an event, which for example is raised from a thread pool thread for example in case of SerialPort, then the event handler will also use the thread pool thread and it is therefore not allowed to call methods of the UI thread except for four thread safe methods - Invoke, BeginInvoke, EndInvoke and CreateGraphics (described later).


 
 

Application.DoEvents

In two cases, it may be desiable to empty the message queue.

  • To keep the UI thread responsible while a big UI job is running. Note that it is not recommended to run big jobs on the UI thread!

  • To ensure that the UI is updated before a thread goes to sleep. In this way, the user will not notice that the UI thread is dead for for example 200 mS. (S)he sees an immediate responce to the mouse click and before (s)he can click the mouse again, the UI thread is ready. During the sleep pause, mouse movements are not blocked as they are not handled by the message queue, but send directly to WndProc.

It is possible to empty the message queue by calling System.Windows.Forms.Application.DoEvents(). Note that it is not necessary to specify System.Windows.Forms - just Application.DoEvents(), and you may get a build error if you imports System.Windows.Forms, which is actually quite un-logical.

If you use Application.DoEvents, you should be aware of two problems:

  • There is a very small chance that one of the messages, which get processed, is a message to close down the window. The routine, which calls Application.DoEvents(), must be able to handle this. Maybe there is no longer any window or form when Application.DoEvents() returns. You can handle this problem by letting the form close event set a flag, which you can test after Application.DoEvents().

  • There is a small chance that one of the messages is the same message as the one, which gets processed. This is know as the re-entrance problem.

    WARNING! The re-entrance problem can be really nasty and very difficult to handle. In case of a simple message without any data, you can just skip any code in subsequent calls by means of a flag or a lock and "Exit Sub", but there are numerous situations where you need the message so that this is not possible. You can also disable the control, which sends the message - for example "Button1.Enable = False", and in this way reduce the probability for a re-entrance problem, but the method is not "water tight" as there can already be more messages of that type on the message queue. For these reasons: Do not use Application.DoEvents() unless it is the only resonable way out to solve a problem or flaw in something you cannot change - for example SerialPort or bad drivers.



 
 

Message Structure

Each message have the following structure:

   (hWnd As IntPtr, msg As Integer, wParam As Integer, lParam As IntPtr)
   

HWnd is the handle for the control (window) that shall receive the message. Msg is the message ID number like WM_CLICK. WParam is used for small pieces of information like flags and LParam is used to points to an object if more information is needed for the message like BulletedListEventArgs and PaintEventArgs. For example, WM_MOUSEMOVE uses wParam for the state of the ALT, Shift, CTRL and mouse buttons and lParam for the mouse coordinates.


 
 

Message Posting

Windows has two basic functions, which are used to post messages - SendMessage and PostMessage. These are Win32 API routines, which you can normally forget all about, but they may be useful in some situations to send messages to controls or forms. If you are a beginner, just skip this chapter.

If you for example wants to send the message WM_LBUTTONDOWN to TextBox2, you can do it like this:

   Imports System.Runtime.InteropServices

       Public Declare Auto Function SendMessage Lib "user32.dll" Alias "SendMessage" _
           (ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, _
            ByRef lParam As IntPtr) As IntPtr

       Const WM_LBUTTONDOWN As Integer = &H201
       SendMessage(Me.TextBox2.Handle, WM_LBUTTONDOWN, 0, 0)
   

PostMessage and SendMessage works differently. PostMessage just sends the message to the message queue of the target window and returns immediately after. SendMessage blocks the caller till the message gets processed by the message pump. The subtle but important difference is that messages sent using SendMessage aren't queued in the message queue whereas PostMessage messages are. SendMessage messages are directly "sent" to the message pump. The message pump retrieves and processes messages sent using SendMessage before looking into those in the message queue. Effectively, there are then two queues, one for SendMessage messages and one for PostMessage messages (which is what we call the message queue). The message pump processes all messages in the first queue before starting with the second. An interesting observation is that if code does a SendMessage from within the message pumping thread, the window procedure (WndProc) gets called directly, that is, it doesn't go through the message pump. Note that in both cases, the message is executed on the thread that created the window - not on the thread on which PostMessage or SendMessage is called! SendMessage has a 5 seconds timeout for the target window to process the message. If the message is not processed within this time, the call finishes and sets ErrorLevel to the word FAIL.


 
 

Delegates

If you want to call more methods with the same set of inputdata (Data1, Data2) you can of course just write:

   Subroutine1(Data1, Data2)
   Subroutine2(Data1, Data2)
   ReturnValue = Function1(Data1, Data2)
   

However, if you want the three methods to be called from a standard method build into .NET, you need another way to do it. Of course standard methods cannot call subroutines and functions, which was unknown when the method was written.

You also need another way if you want to change the subroutines and functions to call dynamically while the program is running (not fixed at compilation time).

.NET has a general usable way to handle this. This is the so-called delegate. A delegate is an object, which has a list called the invocation list, which is able to hold pointers to one or more methods to call (Subroutine1, Subroutine2 and Function1 in the above example). The delegate also has three methods - Invoke and BeginInvoke/EndInvoke, which you can call when you want the methods in the invocation list to be executed like:

   NameOfDelegate.Invoke(Data1, Data2)
   ReturnValue = NameOfDelegate.Invoke(Data1, Data2)
   

Actually, you don't need to write ".Invoke". You can just use the delegate as a proxy for the Invoke method and then just write:

   NameOfDelegate(Data1, Data2)
   ReturnValue = NameOfDelegate(Data1, Data2)
   

In this case, the syntax becomes exactly the same as a normal call, and the delegate may be regarded as a synonym for one or more subroutine(s) or function(s). Remember that a delegate may have many methods in its invocation list, which shall be executed with the same data (Data1, Data2). In case of only one method in the list, it makes sense to talk about a delegate being executed as the help files do, but it is of course not the delegate, which is executed, but the method(s) it addresses.

Because such a call can only return one value, only the last method in the invocation list can be a function. However, it may be quite difficult to ensure which method is the last one so in practice all methods should be subroutines if there are more methods than one. Invoke, BeginInvoke and EndInvoke are described in more details later.

In the definition of the delegate it shall be specified whether the last method to call is a function or a subroutine. In case of a subroutine, the delegate is defined as "Public Delegate Sub ..." or else it is defined as "Public Delegate Function ..." It is also necessary to specify any argument(s) to transfer and whether these should be transferred by value (ByVal) or by reference (ByRef). If we for example want to transfer a string by value to a subroutine, the whole definition becomes:

   Public Delegate Sub NameOfDelegate(ByVal Buffer As String)
   

When you define a delegate, the compiler actually creates a complete class definition to support this delegate. If you for example define the following delegate:

   Public Delegate Function TwoInput(ByVal obj1 As Object, ByVal Str1 As String) As Integer
   

The compiler will generate a new class that looks more or less like this code:

 
   Public Class TwoInput
       Inherits System.MulticastDelegate 

       Public Sub New( _                           ' Constructor with initialization data
           target As Object, _                     ' Pointer to any object
           method As Int32)                        ' Pointer to method
       End Sub

       Public Overridable Function Invoke( _       ' Add Invoke method
           ByVal obj1 As Object, _                 ' First argument
           ByVal str1 As String) _                 ' Second argument
           As Integer                              ' Return value from invoked method
       End Sub

       Public Overridable Function BeginInvoke( _  ' Add BeginInvoke method
           ByVal obj1 As Object, _                 ' First argument
           ByVal str1 as String, _                 ' Second argument
           ByVal anyCallback As AsyncCallback, _   ' Optional callback delegate
           ByVal anyObject As Object) _            ' Optional arguments for callback
           As IAsyncResult                         ' Any returned value = Present state
       End Function

       Public Overridable Function EndInvoke( _    ' Add EndInvoke method
           ByVal result As IAsyncResult) _
           As Integer                              ' Return value from invoked method
       End Sub

   End Class
   

The new class TwoInput is derived from MulticastDelegate and as such it inherits the methods, properties and fields of this base class like the methods Combine, Remove, ToString etc., the properties Target and Method and the Previous field, which is used to link delegates together. This is used to implement the invocation list.

The target object field is only used in case the delegate contains a reference to an instance method like e.g. Object1.Method1. In this case, the compiler puts a pointer to Object1 into the target field and a pointer to Method1 into the method field. If the delegate refers a shared method (no object), the compiler sets the target pointer to Null. The two properties Target and Method of the delegate refers to these two fields.

AsyncCallback is an optional delegate, which points to a method, which shall be executed when the asynchronous call completes. If the callback method needs any arguments these are transferred in the following object (anyObject).

IAsyncResult is an interface object, which represents the present state of an asynchronous operation and therefore can be used to monitor the progress of a BeginXXX call like BeginInvoke.

If the method to invoke is a subroutine, the new Invoke and EndInvoke methods are also subroutines. If the method to invoke is a function like in this case, the Invoke and EndInvoke methods are also functions, which returns the return value of the invoked method (integer in this case).

The reason why the three methods Invoke, BeginInvoke and EndInvoke are build by the compiler and not inherited from the base class multicastDelegate is that the way you call a method shall be the same whether you call it directly or by means of a delegate, and because the number of arguments varies, you cannot use a standard method or would have to combine all arguments in a single object. Control.BeginInvoke, which is described later, is a very good example of the necessary limitations if the compiler did not tailor cut the three methods to the actual needs. Use of the three methods is described in details below.

Delegates are sometimes described as type-safe function pointers because they are similar to function pointers used in other programming languages like C, but unlike function pointers, which can only reference shared methods, that is, subroutines and functions that is called without a specific instance of a class, delegates can also reference instance methods (Instance.Method). "Type-safe" are usually regarded as added safety, because the signature of the delegate must match the arguments of the subroutine(s) or function it addresses, but the signature is not there for safety. As can be seen, the signature is necessary to be able to build the Invoke and BeginInvoke methods (how many arguments are needed and of which type). It is also necessary to be able to decide whether Invoke and EndInvoke shall be subroutines (no returned value) or functions. Because the Invoke and BeginInvoke methods must be able to call the methods in the invocation list in exactly the same way as a direct call, the delegate must have the same signature (number and type of arguments) as the method(s) to call. Therefore, you cannot use a delegate to point to a method with other arguments than specified in the definition.

When you declare a delegate, it is in principle not different from the declaration of normal data types like Bytes, Integers, Arrays and Strings. You can even load the address of a method to call in the same way as you can load data into the various data types during declaration:

   Dim DisplayRoutine As NameOfDelegate = AddressOf Display  ' or
   DisplayRoutine = New NameOfDelegate(AddressOf Display)
   

Note that VB uses the keyword AddressOf to be able to destinguish between the address of a method and a returned value from a function.

 
 

Standard Delegates

.NET has many standard delegates. The most important of these are defined as:

   Public Delegate Sub MethodInvoker()
   Public Delegate Sub EventHandler(sender As System.Object, e As System.EventArgs)
   Public Delegate Sub SerialDataReceivedEventHandler _
       (sender As Object, e As SerialDataReceivedEventArgs)
   

MethodInvoker is used to invoke a method with no arguments at all. MethodInvoker increases the efficiency internal in .NET by cutting some corners, so it is recommended to use this delegate if you have no arguments to transfer and does not need information about the sender, which raises the event.

EventHandler is used if you need information about the object that raises the event. The signature in the EventHandler delegate defines a subroutine whose first parameter (sender) is of type Object and refers to the instance that raises the event, and whose second parameter (e) is derived from type EventArgs and holds any event data. If the event does not generate event data, the second parameter is simply an instance of EventArgs. Otherwise, the second parameter is a custom type derived from EventArgs and supplies any fields or properties needed to hold the event data. Note however that control.Invoke and control.BeginInvoke (described later) set the sender to the original caller and EventArgs to EventArgs.Empty - even though you have supplied your own EventArgs, so don't use Eventhandler to marshal any call with arguments to another thread (described later)!

EventHandler is used a lot in .NET. If you for example in Windows Forms designer drag a button onto your form and double click on it you will see that the designer automatically generates the following event handler for you:

   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
       Handles Button1.Click

   End Sub
   

As you can see, it automatically inserts the two arguments of the EventHandler delegate. The reason why these arguments are included is that Button1_Click is going to be invoked with an EventHandler delegate, and a delegate and the method to invoke must always have the same arguments. Usually you have a method, which you want to invoke, and then define a delegate to do this, but this is the other way around. Your subroutine is going to be invoked with the system delegate EventHandler, which cannot be changed, so your subroutine must have the same arguments or else you will get an error message.

"Handles Button1.Click" indicates that this subroutine will handle the event Click from the sender Button1. It is possible for many subroutines to handle the same event, and it is possible for one subroutine to handle many events provided that they have the same signature. In this case, the various events are just separated with comma like "Handles Button1.Click, Button2.Click".

If the keyword "Handles" is used, the sender(s) must always be declared "WithEvents" like:

   Friend WithEvents Button1 As System.Windows.Forms.Button
   

This is the way Button1 is declared by Windows Forms designer in the above example. WithEvents is explained in further details in chapter "Event Keywords".

SerialDataReceivedEventHandler is the delegate, which holds the event handler(s) for the DataReceived event. You can usually forget all about this delegate except if you want to put your serial port code in its own class. In that case, it may be necessary to declare a new instance of the SerialDataReceivedEventHandler and point it to your event handler to make the DataReceived event work like this:

   Public WithEvents COMPort As New System.IO.Ports.SerialPort
   
   Private COMDelegate As New System.IO.Ports.SerialDataReceivedEventHandler _
       (AddressOf Receiver)

   Private Sub Receiver(ByVal sender As Object, _
       ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles COMPort.DataReceived
       ...
   End Sub
   

Note that the names of the standard delegates are quite confusing. Microsoft has chosen the name XxxEventHandler for a delegate that has one or more event handlers in its invocation list - not for the name(s) of the event handler(s) itself/themselves!


 
 

Event Keywords

To make it easier to work with events, VB has some event keywords such as Event, RaiseEvent, WithEvents, Handles, AddHandler, and RemoveHandler. However, they simply abstract the creation and processing of delegates.

Event is just a replacement for "Delegate Sub". The delegate is always defined "Sub" because the invocation list can contain many methods (eventhandlers) so it is impossible to return one value (you never know which method is the last one in the invocation list). Therefore an event cannot be declared "... Event ... As AnythingElseThanASubDelegate".

   Public Class MyClass
       Public Event MyEvent1()                                          ' Event with no arguments

       Public Event MyEvent2(ByVal Inp1 As Integer, ByVal Inp2 As Byte) ' Event with two arguments

       Public Delegate Sub TextDelegate(ByVal Text As String)           ' Delegate with string data 
       Public Event MyEvent3 As TextDelegate                            ' Event based on sub delegate

   End Class
   

When the compiler encounters an event keyword in your class, it creates the event delegate (for example MyEvent1) and two public methods Add_EventName and Remove_EventName. These methods allow delegates, which point to eventhandlers, to be combined (added) or removed from the invocation list of the event delegate like this:

       Private Sub MyEventHandler
           ...
       End Sub

       Private Sub/Function ...
           ...
           AddHandler MyEvent, AddressOf MyEventHandler
       End Sub/Function

       Private Sub/Function ...
           ...
           RemoveHandler MyEvent, AddressOf MyEventHandler
       End Sub/Function
   

MyEvent is the name of the event and MyEventHandler is the subroutine to be added to the invocation list of MyEvent. As you can see, eventhandlers may be added and removed while the program is running. However, if you add an eventhandler, it is very important that you remember to remove it again if you intend to dispose the object, which raise the event. The reason for this is that the automatic garbage collector of .Net does not remove an object from memory as long as it is referenced by eventhandlers. To ensure that you don't forget and to be able to specify one or more eventhandlers at compilation time, VB has two more keywords "WithEvents" and "Handles", which are used together (Handles cannot be used without WithEvents) as shown below:

   Public Class MainClass
       Private WithEvents MyInstance As MyClass  ' This class includes the event MyEvent

       Private Sub MyEventHandler(sender As Object, e As EventArg) Handles MyInstance.MyEvent
           ...
       End Sub

   End Class
   

If the event is defined in your own class, you can use the keyword Me instead of MyInstance:

       .... Handles Me.MyEvent
   

It is possible for a subroutine to handle many events. In this case, the events are just specified as a comma separated list like this:

       .... Handles Button1.Click, Button2.Click, Button3.click
   

Handles is just an initialization! You can later use RemoveHandler and AddHandler to remove and add your eventhandler from the invocation list.

When an object is declared WithEvents, the get and set methods to reference and build and dispose the object is overrided by two new methods as shown below:

       Private WithEvents cmd As Button

       Sub Eventhandler1(...) handles cmd.Click
          ...
       End Sub

       Sub Eventhandler2(...) handles cmd.Click
          ...
       End Sub

       Sub Eventhandler3(...) handles cmd.Command
          ...
       End Sub
   

Courses the button declaration to be changed to:

   Private Shared _cmd As Button            ' New object with a slightly different name

   Private Shared Property cmd As Button    ' Original object name changed to a property
       Get                                  ' New Get method just points to the old one
           Return _cmd
       End Get

       Set(ByVal WithEventsValue As Button) ' New Set method with invocation list handling
           Dim handler As MouseEventHandler = New MouseEventHandler(AddressOf Eventhandler3)
           Dim handler2 As EventHandler = New EventHandler(AddressOf Eventhandler2)
           Dim handler3 As EventHandler = New EventHandler(AddressOf Eventhandler1)
           If (Not_cmd Is Nothing) Then
               RemoveHandler _cmd.MouseClick, handler
               RemoveHandler _cmd.Click, handler2
               RemoveHandler _cmd.Click, handler3
           End If
           _cmd = WithEventsValue           ' Call the old set method
           If (Not _cmd Is Nothing) Then
               AddHandler _cmd.MouseClick, handler
               AddHandler _cmd.Click, handler2
               AddHandler _cmd.Click, handler3
           End If
       End Set
   End Property
   

Note that the original name is changed to a property with a get method, which points to the object. This courses a slight overhead each time you reference the object, but this is the price to pay for a better program overview and reliability. EventHandler and MouseEventHandler are standard delegates so it is actually delegates (handler, handler2 and handler3), which are added to the invocation list in the AddHandler statements. The invocation list is a list of delegates, which points to the methods to be executed, but when you use AddHandler yourself, you can forget this. VB does this for you.

Note what happens if you set Button to nothing, that is, dispose the object. The set method is called with null (nothing) as input, but _cmd has not yet been changed, so the three eventhandlers are removed from the invocation lists of the two events. In the following statement _cmd is set to null so the AddHandler statements are not executed.

RaiseEvent is basical a replacement for Delegate.Invoke, but it also adds a check for null so that you don't invoke a delegate, which does not excist. To avoid race conditions in multithreaded environments, the check is done on a temporary variable. In C#, you have to do this check yourself.

       RaiseEvent MyInstance.MyEvent(AnyData)
   

Is replaced by:

       Dim TempValue As MyEvent = MyInstance.MyEvent
       If (Not TempValue Is Nothing) Then
           TempValue.Invoke(AnyData)
       End If
   

This executes all methods in the invocation list. Even though an event is just a delegate, you cannot invoke it directly in VB, but need to use the RaiseEvent keyword. This ensures that you always do it right by the book.

   Public Class MyClass
       Public Event MyEvent(ByVal Sender As Object)

       Private Sub MyEventHandler(ByVal Sender As Object) Handles Me.MyEvent
           ...
       End Sub

       Private Sub/Function ...
           ...
           RaiseEvent MyEvent(Me)
       End Sub
  
   End Class
   

Note that the signature of the event (delegate) and the eventhandlers (methods) must always be the same.


 
 

Invoke, BeginInvoke
and EndInvoke

Once we assign an address to a delegate, we can use the Invoke or BeginInvoke method to call the subroutine or function with the wanted input data in the same way as the input data for a normal subroutine or function is also supplied in the call like MethodToCall(SomeData).

There are two ways of Invoke and BeginInvoke calls - delegate.Invoke/BeginInvoke and control.Invoke/BeginInvoke.

  • Delegate.Invoke just executes the methods in the invocation list on the present thread in the same way as normal method calls. Delegate.Invoke is used each time an event is raised. As described previously, RaiseEvent is simply translated to a delegate.Invoke call.

  • Delegate.BeginInvoke needs to return before the job is done. Therefore it makes a System.Threading.ThreadPool.QueueUserWorkItem() call, which graps a thread from the thread pool and uses this for the method if a thread is available. If no thread is available in the pool, the delegate is put in queue until a thread becomes available. The call returns as soon as the job is started on the thread pool thread.

  • Control.Invoke either executes the method(s) in the delegate directly if it is called from the same thread as the one where the control was generated (usually the UI thread), or it sends a message to the message queue of the thread where the control was generated and let the message pump start the method(s). In this case, the method will be executed on the UI thread instead of the thread from which control.Invoke is called so that it can call any methods of a control created on the UI thread (no need to be thread safe). Control.Invoke does not return before the job is done!

  • Control.BeginInvoke is similar to control.Invoke except that it always post a message to the message queue and that it does not wait until the job is done, but returns immediately as soon as the message has been appended to the message queue!

Control.Invoke and control.BeginInvoke are for example very useful if a worker thread wants to write something to a TextBox like TextBox1.Text = "Hello World!". Because it is not allowed to call the Text method directly, it must invoke a function or subroutine on the UI thread, which can do the job for it. This is called to Marshal the call to the UI thread. Invoke, BeginInvoke, EndInvoke and CreateGraphich are the only methods of another thread that you are allowed to call directly unless you set the control.CheckForIllegalCrossThreadCalls property to false, which is not recommended!

You may say that you use delegate.BeginInvoke if you want to hire a worker temporary to do a job for you. You use control.BeginInvoke or control.Invoke if you want the job to be done by a staff worker (Me). You may also say that you use BeginInvoke if you just want to give the order and handle over the key to your home, and you use Invoke if you want to be home until the job is done.

Control.BeginInvoke may be called as a subroutine with no returned value or as a function. Delegate.BeginInvoke is always called as a function. In case of a function, they both return an IAsyncResult. As described previously, IAsyncResult is an interface object, which represents the present state of an asynchronous operation. It is useful if you for example want to wait until the operation is finished. The simplest way to do this is to use the EndInvoke method. As shown under delegates, this method uses the IAsyncResult as input. If the method you have invoked with BeginInvoke is a function, the EndInvoke statement will return the result of this function. In case of an exception inside the method, the exception is also passed back out through the EndInvoke call.

   Dim PresentState As IAsyncResult = _
       [delegate or control].BeginInvoke(New Delegate1(AddressOf Display), StringToWrite)
   .....                                   ' Some code
   Try
       [delegate or control].EndInvoke(PresentState)  ' Wait for end of display operation
   Catch ex As Exception                   ' Get any exceptions from the Display routine
       MsgBox(ex.Message & " in Display")  ' Display any exception
   End Try
   

[delegate or control] is the name of a delegate in case of delegate.BeginInvoke/EndInvoke and the name of a control in case of control.BeginInvoke/EndInvoke

Invoke is nothing but a BeginInvoke call followed by an EndInvoke call like this:

   AnyReturnedValue = _
   [delegate or control].EndInvoke([delegate or control].BeginInvoke(YourDelegate, AnyObject))
   

To ensure that control.Invoke do not get higher priority than control.BeginInvoke, Invoke and BeginInvoke both do a Windows API PostMessage call - even though Invoke is similar to a SendMessage call (a message posted with SendMessage is executed immediately and therefore has higher priority than a message posted with PostMessage). So a pair of BeginInvoke/EndInvoke calls are just as synchronous as Invoke. You may say that if you call EndInvoke to wait until the job is done, it is synchronous. If you don't, it is asynchronous.

NOTE. Delegate.EndInvoke also has the function that it releases the thread to the pool when the job is done. Therefore, delegate.BeginInvoke must always be matched with a delegate.EndInvoke call (the worker must back to the company when the job is done). This is the reason why you cannot call delegate.BeginInvoke as a subroutine because then there is no IAsyncResult as input for the EndInvoke statement. In case of control.BeginInvoke, it is not necessary with EndInvoke because it uses the UI thread (or another thread with a message pump) so there is no thread to release (job done by a staff worker).


 
 

Control.
Invoke/BeginInvoke

According to the help file in VB.NET, control.BeginInvoke executes a delegate asynchronously on the thread that the control's underlying handle was created on. It is hard to imagine a more cryptically explanation and it is not even true! First of all, a delegate is not something, which is executed. It is just an object pointing to a method. If you know what really happens, it is OK to use the shortcut and just describe it that way, but it is very confusing to a beginner, who may think of a delegate as some kind of subroutine or function since these are the only ones that are executed, and the sub or function keyword in the definition may also lead your thoughts in that direction. Second, it is not BeginInvoke, which execute the function, but WndProc, which is called from the message pump. This makes a tremendous difference. If it was the BeginInvoke method, the method would be executed immediately and therefore get higher priority than messages already waiting on the message queue, and BeginInvoke would not return before the job was done. Besides, the method would be executed on the worker thread and would therefore not be able to call methods of controls generated on the UI thread, so nothing would be gained. This would be similar to a Windows API SendMessage call, and this is not what happens! Don't feel stupid if you don't understand the help files. How can an intelligent person understand a wrong explanation?

All control objects plus the form classes inherit the Invoke and BeginInvoke method from their common base class System.Windows.Forms.Control as mentioned previously, so they all have this method. You can therefore choose any control object or the form itself (Me). It is usually recommended to use the form (Me) since it is less confusing than using a control. If you for example use a control and write TextBox1.BeginInvoke, it looks like you use the BeginInvoke method of the TextBox to write the data. You don't. You just borrow its BeginInvoke method to invoke another method on the UI thread, which may or may not write to the TextBox. What you also do with the selected form or control is to use its the so-called ThreadCallbackList if it exists or else it is generated. This list is used to hold a so-called TME (ThreadMethodEntry) object, which holds all data you want to send to the UI thread including a delegate that points to the method to be executed and an object with the arguments. The TME object is also used to transfer any returned data and exceptions from the method back to the calling method (on the other thread). This is why it is called the ThreadCallbackList.

Control.Invoke and control.BeginInvoke have only one or two arguments. The first one is a delegate that points to the method(s) you want to call. The second one is an object, which must hold all arguments to transfer to the method(s). As soon as you have made the Invoke or BeginInvoke call, the argument data are copied to the TME object (descibed below). This allows you to reuse the same argument object for new data. The TME object is then put on the ThreadCallbackList of the control you have selected in the Invoke or BeginInvoke call.

If you for example want to send the string "StringToWrite" to the Display routine, which writes to TextBox1, a control.BeginInvoke statement may look like this:

   TextBox1.BeginInvoke(New NameOfDelegate(AddressOf Display), StringToWrite) ' or
   Me.BeginInvoke(New NameOfDelegate(AddressOf Display), StringToWrite)
   

If you don't want to transfer anything, but just want to invoke the Display method, you can use the standard delegate MethodInvoker (or EventHandler). In this case, you don't need to define any delegate, but can just write:

   TextBox1.Invoke(New [MethodInvoker or EventHandler](AddressOf Display)) ' or
   Me.Invoke(New [MethodInvoker or EventHandler](AddressOf Display))
   

However, if the subroutine will be invoked with the EventHandler delegate, it must have the same signature, that is, (ByVal sender As System.Object, ByVal e As System.EventArgs).

These two methods are the typical way control.BeginInvoke and control.Invoke is used in a serial port program like this one.

Behind the scenes, control.Invoke and control.BeginInvoke are rather complicated and time consuming. If you are a beginner, just skip the rest of this chapter.

When you call Control.BeginInvoke() or Control.Invoke() the first thing that happens is that the logic looks up the so-called marshaling control by means of the function FindMarshalingControl. The marshaling control is the first control including the current one that has a handle associated with it and therefore can be used in a Windows message (see chapter Message Structure). Usually, the marshaling control will be the one you have used in your BeginInvoke or Invoke statement, but if this control does not have a handle at the time of the BeginInvoke or Invoke call, the logic looks to the parent control, to the parent of the parent, and so on, until it finds a control, which has. If it cannot find any, it still has a way out because all threads, which have a message pump apparently, have a so-called Parking Window, which can be used. The logic will then use the marshaling control in the actual BeginInvoke or Invoke call, that is, call MarshalingControl.BeginInvoke or MarshalingControl.Invoke.

The only thing these two calls do is to calls the function MarshaledInvoke. The following code shows what this routine does with all minor details like most definitions, internal flags, locks and exception handling omitted.

   Private function MarshaledInvoke(_
       caller As Control,_                     ' Original control in BeginInvoke or Invoke call
       method As Delegate,_                    ' Delegate, which points to the method to execute
       methodArgs As Object,_                  ' Any delegate argument - Note only one
       synchronous As Boolean)_                ' True if Invoke, False if BeginInvoke
       As Object

       Dim Local As Boolean = False
       Dim CompStack As CompressedStack = null
       Dim TCList As Queue                     ' ThreadCallbackList

       If (not InvokeRequired) And synchronous Then
           Local = True                        ' Control.Invoke is called on the UI thread
       Else
           CompStack = GetCompressedStack()
       End If


       TMEObject = New ThreadMethodEntry(_     ' Build ThreadMethodEntry object
           caller,_
           method,_
           methodArgs,_
           synchronous,_
           ExecusionContext)

       ' Build ThreadCallbackList if it does not exist
       TCList = Me.Properties.GetObject(control.PropThreadCallbackList) ' Get ThreadCallbackList
       If TCList = Null then
           TCList = new Queue()
           Me.Properties.SetObject(control.PropThreadCallbackList, TCList)
       End If

       ' Build a custom message if it does not excist
       If Control.ThreadCallbackMessage = 0 Then
           control.ThreadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(_
               Application.WindowsMessageVersion &_ ' WindowsForms20 for version 2.0
               "_ThreadCallbackMessage")
       End If
       TCList.Enqueue(TMEObject)               ' Queue the TME Object

       If Local Then
           Me.InvokeMarshaledCallbacks()       ' Execute all TME's immediately
       Else
           UnsafeNativeMethods.PostMessage(_   ' Post the message to the message queue
               new HandleRef(Me, Me.Handle),_  ' Handle for the marshaling control (hWnd in message)
               control.ThreadCallbackMessage,_ ' The custom message
               0, 0)                           ' no wParam or lParam arguments
       End If

       If Not synchronous Then                 ' Control.BeginInvoke returns here
           Return TMEObject
       End If

       If Not TMEObject.IsCompleted            ' Wait for operation to finish
           Me.WaitForWaitHandle(TMEObject.AsyncWaitHandle)
       End If

       If TMEObject.Exception <> Null Then     ' Throw any exceptions from delegate method
           Throw TMEObject.Exception
       EndIf

       Return TMEObject.retVal                 ' Return any value from delegate method

   End Function
   

The caller is the original control used in the BeginInvoke or Invoke statement. If that control had a handle at the time of the call, the caller will be equal to the marshaling control.

Me refers to the current control, that is the control used in the call of MarshaledInvoke, so "new HandleRef(Me, Me.Handle)" will return the handle of the marshaling control. It will then be this control (control.WndProc), which will receive the message from the message pump on the UI thread.

Note that the delegates and the arguments for the method(s) are not transferred on the message queue. Instead each call of control.Invoke or control.BeginInvoke wraps them into a ThreadMethodEntry object together with the inputdata for the methods and a lot of other information and post this object to the ThreadCallbackList of the marshaling control (the list is build if it does not exist). The call then sends the custom message WindowsForms20__ThreadCallbackMessage to the message queue. The message pump will then dispatch the message to WndProc of the marshaling control, which will then execure the delegates in all ThreadMethodEntry objects in its ThreadCallbackList one by one by means of the InvokeMarshaledCallbacks method. In case of control.Invoke, MarshaledInvoke calls InvokeMarshaledCallBacks directly if the controls are generated on the present thread (message queue not used). Note that InvokeMarshaledCallbacks courses execution of the delegates of all TME objects in the ThreadCallbackList, so the message you post may be executed sooner as you expect if somebody calls control.Invoke on the same control from the UI thread (executed immediately) or there already is a WindowsForms20_ThreadCallbackMessage on the message queue for that control! You need to be aware than any synchronous call can possibly affect your asynchronous calls and cause them to be processed. Also you cannot expect any synchronization between events generated on the UI thread and events generated on other threads.

The TME object contains 5 things:

  • The original control used in the BeginInvoke or Invoke statement.

  • A delegate (method) with an invocation list containing the method(s) to call on the UI thread.

  • One object (methodArgs) that holds all necessary input arguments for the method(s) in the invocation list of the delegate. Note that unlike a normal call by means of a delegate, it is only possible with one object, so if more arguments are needed, they must be wrapped in a common argument object.

  • A Boolean value (synchronous), which is True in case of an Invoke call and False in case of BeginInvoke.

  • The ExecutionContext, which includes a lot of information like HostExecutionContext, IllogicalCallContext, LogicalCallContext, SecurityContext and SynchronizationContext, where the SecurityContext includes the so called compressed stack, which is a compact representation of the safety (CAS-relevant) informations on the call stack. The reason why the compressed stack is included is that when InvokeMarshaledCallbacks is called on the UI thread, it invokes a delegate that has been initialized on a different thread that may have different permissions. Therefore, the methods on the UI thread are run on the compressed stack (copy of call stack) so you can't try to Invoke code on the UI thread that you wouldn't have been able to run on your own thread.

As can bee seen, TME objects and their execusion on the UI thread involves a huge overhead if you only want to send a "character-received-beep" to the UI thread. In many ways, Windows are nothing but an object building, object disposing, byte copying machine with very little payload :-) Therefore, it is highly recommended to collect a full telegram before you send it to the UI thread. From a serial port point of view, you can easily call control.BeginInvoke each time your eventhandler for the DataReceived event runs (this is done in the sample program) because you will just transfer more bytes in case of a heavy load condition, but other Windows programs may suffer from this. In case of BeginInvoke, you may also use a global flag to limit the number of messages on the message queue (Invoke is synchronous and will not let you post a new message before the previous one is processed). Before you call BeginInvoke, you test a flag, which could be Boolean or for example a 32-bit value (there is no way a 32-bit value can be half updated due to the pre-emptive multitasking). If the flag is False or the value is Null, you set the flag or value and call BeginInvoke. If the flag is True or the value different from Null, that is, there is already a message of that type on the message queue, you don't call BeginInvoke, but you may update the value. The method on the UI thread clears the flag (False) or value (Null) when it is used.

Control.Invoke and control.BeginInvoke are programmed in such a way that they are thread safe, that is, able to work on the shared message queue and ThreadCallbackList - even if they are called from more different threads at the same time. This is obtained by means of some locks (not shown).


 
 

UART

Al transmissions in and out from the serial port takes place by means of a UART (Universal Asynchronous Receiver Transmitter), which is an integrated circuit in the PC. All data are transmittet asynchronous in the so-called NRZ (Not Return to Zero) format. Each byte is transmittet as a start bit (logical 0) followed by 7 or 8 data bits with the least significant bit first, any parity bit and a stop bit (logical 1) as shown below:

----         ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ----
    |       |       |       |       |       |       |       |       |       |       |       |
    | Start | bit 0 | bit 1 | bit 2 | bit 3 | bit 4 | bit 5 | bit 6 | bit 7 | Pari. | Stop  |
    |       |       |       |       |       |       |       |       |       |       |       |
     ------- ------- ------- ------- ------- ------- ------- ------- ------- -------
   

Note that bit 7 and the parity bit are optional. In the early days, 5 or 6 data bits and/or 1.5 or 2 stop bits was also used, but this is newer used anymore and USB-Serial adapters usually do not support less than 7 bits.

There are two reasons for transmitting the least significant bit first.

  • The UART is designed to be used with the so-called little-endian or Intel model where all data are right shifted and the least significant byte or word is stored on the lowest address. Because of the right shifting, it is bit 0 (the least significant bit), which has a fixed position, no matter if the data is a byte (bit 0-7), a word (bit 0-15) or a long word. Therefore, it is most practical to transmit this bit first because depending on the telegram length the last bit may be bit 7, bit 15, bit 63 or anything else. If you ignore the start bit and the stop bit and transmits all bits from one address and up, the stream of bits will start with the least significant bit of the least significant byte and end with the most significant bit of the most significant byte.

    In systems, which use the big-endian or Motorola model, all data are left shifted, so that the most significant bit of a byte has the same position as the most significant bit of a word or long word. Therefore, it ought to be the most significant bit, which is called bit 0, but most big-endian systems except for the PowerPC are not so consistent. With the big-endian model, the most significant byte is stored on the lowest address and it is natural to transmit the most significant bit first.

  • It is most practical with a variable number of data bits (5, 6, 7 or 8) and parity.

Advanced UART's have a FIFO (First In, First Out) buffer on the transmitter and the receiver, so that the computer can transmit and receive in bursts and do not need to respond for each byte. The standard UART in the PC (16C550) has a 16 byte transmitter FIFO and a 16 x 11-bit receiver FIFO, which not only contains the received byte, but also three flags, which indicate a break condition, that is, a condition where all bits including the stop bit are logical 0, a framing error, which indicates that the received stop bit is not logical 1 (except for break) and a parity error, which indicates an error on the odd or even parity. Advanced UART's are also able to use the parity bit as a 9th bit to distinguish between address and data and very advanced UART's like 16C950 are able to perform the address comparison automatically, which is very useful on multidrop lines. They even have a 9-bit wide transmitter FIFO to be able to transmit the 9th bit automatically.

As described previously, the interrupt latency of Windows is approximately 1 mS so with a 16 x 11-bit receiver FIFO it is able to receive up to 115.2 kbit/s. At this speed, it takes 1.4 mS to fill the FIFO. If higher transmission speeds are needed, it is necessary to use more advanced UART's. At for example 921.6 kbit/s you need a UART with a 128 x 11-bit receiver FIFO like 16C850 or 16C950.


 
 

RS-232

The PC uses voltage levels according to the RS-232 standard to transmit the data. A logical 0 or "on" is the active state, which corresponds to a positive voltage level between +3V and +15V, and a logical 1 or "off", which is the passive state, corresponds to a negative voltage between -3V and -15V. The default line state is logical 1 (passive) so unfortunately it is not possible to use the Break detection to determine if a unit is connected. It is very important to notice that SerialPort uses "True" as the active state so that true corresponds to logical 0 and not logical 1 as you would expect!

The input impedance of a RS-232 receiver is approximately 5 Kohm. The trig level is not 0V, but approximately 1.4V (with a little hysteresis). This makes an RS-232 input compatible with all logic families (TTL, CMOS etc.) with a supply voltage of 3.3V or 5V. This is very useful, because it enables you to connect a modem signal input directly to a logic signal and utilize the event (see next chapter). Note however that on old RS-232 receivers, which has a 5 KOhm serial resistor instead of a 5 KOhm pull down resistor (modern receivers), the switching times - especially the turn off time - may be increased considerably due to the reduced input current.


 
 

I/O Signals

A standard 9-pin serial connector contains the following signals (pins). The signals may be set or read with the specified properties:

  1. CD (Carrier Detect). Input, which may be read with the CDHolding property.
  2. RXD (Receiver Data). Input. Steady state may be read with BreakState.
  3. TXD (Transmitter Data). Output. Steady state may be set with BreakState.
  4. DTR (Data Terminal Ready). Output, which may be set (or read) with DtrEnable.
  5. SGND (Signal ground). Common return signal.
  6. DSR (Data Set Ready). Input, wich may be read with DsrHolding.
  7. RTS (Request To Send). Output, which may be set (or read) with RtsEnable.
  8. CTS (Clear To Send). Input (usually a response to RTS), which may be read with CtsHolding.
  9. RI (Ring Indicator). Input, which cannot be read with SerialPort.

Note that if you set BreakState, DtrEnable and/or RtsEnable true, the output(s) will go to a positive voltage. This may be used for a simple I/O system. Note however that the drive power is very limited. The output pins are usually not able to sink or source more than approximately 35-50 mA and in some cases even lower. At 12 V, a standard RS-232 input will only draw 2.4 mA and this is what the drivers are designed for - not for use as power supply although some applications use the signals that way.

It is usually recommended to set DTR and RTS true. If the connected device uses these signals, it will not transmit before the signals are set!

WARNING! The methods used to set these signals use multithreading and therefore do not course the outputs to change immediately! This means that if the time between such statements are less than approximately 11 mS on a single processor system or 16 mS on a multiprocessor system, you cannot expect the time delay to be active. If you for example write:

   COMPort.Breakstate = True
   Sleep(5)
   COMPort.Breakstate = False
   

you may experience that in some cases (in the order of 5%), the two COMPort.BreakState statements are executed immediately after each other, so that the break transmission does not work! Windows is certainly not a Real Time Operating (RTO) system!


 
 

SerialPort Class

Before a serial port can be opened, a new instance of the System.IO.Ports.SerialPort class must be made by means of a statement like this:

   Dim WithEvents NameOfSerialPort As New System.IO.Ports.SerialPort
   

WithEvents means that the new instance is able to raise events like DataReceived when new data are available and PinChanged when any of the moden input signals change state. If you are only going to use SerialPort for a polled system, you may not need any events and may therefore omit WithEvents.

Each instance of SerialPort can only handle one COM port at a time. Therefore, it is necessary to close one COM port before opening a new one. Note that it is necessary with a short time delay to allow the background thread to close down before you are allowed to reopen the port. If you need more COM ports to be open at the same time, you must use more instances of SerialPort.

The new serial port is opened with NameOfSerialPort.Open(). When this happens, all data you have selected in the UI of your UART driver like hardware flow control, FIFO settings etc. are loaded into the driver, and SerialPort tries to set the size of the transmitter buffer and the receive buffer in the driver to the values of the properties WriteBufferSize and ReadBufferSize by means of the API function SetupComm. The default size for the transmitter buffers is 2048 bytes and the default size of the receive buffer is 4096 bytes and .NET ignores any values smaller than these values. The receive buffer should be able to hold all data until the routine, which emties the buffer, gets up and running. Because this may easily take 100 mS, it is recommanded to use a buffer at least 100 times the size of the receiver FIFO in the UART as described previously. If a 16C950 UART is used, it is therefore necessary to increase the buffer size to for example 16384 bytes. It should be noted that many drivers do not support more than 32 kbytes. After the buffer sizes are set, you start to use these buffer. Therefore the sizes cannot be changed while the port is open.

The SerialPort object has yet another buffer on top of the driver buffer. This buffer is only used when using methods dealing with encodings i.e. Chars and Strings, but not bytes. When reading bytes, some bytes may be taken out of the buffer, but not put into it, so if you work only with bytes, the internal SerialPort buffer is newer used. The size of the buffer is 1024 bytes. The property BytesToRead tells the total number of bytes in the two buffers together and can therefore have a value greater than ReadBufferSize (size of buffer in UART driver).

If you are calling a byte-related method like ReadByte or Read(byteArray, Offset, Count), the byte is returned directly or the bytes are put directly into your byte array as they arrives.

If you are calling an encoding-related method such as ReadChar, ReadLine or ReadTo, then the bytes are first put into the internal buffer and then the char-encoded data are put into your buffer or returned as string, etc, depending on the method. Remember that .NET uses 16-bit Unicode for all strings and char's so each received byte is converted to a 16-bit value! The data from the driver buffer are not transferred to the SerialPort buffer before you call the encoding-related method. If you are receiving 16-bit Unicode characters and only one byte is received, then it is assumed that the next incomming byte will complete the character, so the first part is buffered. No half-characters are returned. ReadExisting is a special case because it creates a new buffer with a size equal to the number of bytes available. All data are read out and returned as a string.

By default, strings and char's are transmitted (not coded) as 7-bit ASCII where all characters above 127 are illegal and will be replaced with "?" (code 63) - even though you have defined 8 data bits! Therefore, you cannot use a string or char related method to send or read binary information (0-255) unless you change the encoding to something, which include all values up to 255 like:

   YourSerialPort.Encoding = System.Text.Encoding.GetEncoding(28591)  ' or
   YourSerialPort.Encoding = System.Text.Encoding.GetEncoding(1252)
   

However, why involve an extra receive buffer and encoding/decoding methods if it is not necessary? If you need to transfer binary values, it is better to use a byte related receive method like ReadByte and use the only byte related send method there is - Write(ByteArray, offset, count).

Unfortunately, the width of all buffers is only 8 bits so the synchronization between data, break and error conditions from the receiver FIFO in the UART is lost. It is therefore impossible to separate binary telegrams by means of a break condition or use modern 9th bit communication :-(

It is important to notice, that the flow control you select in SerialPort is based on software and not always communicated to the driver. If you intend to use a UART with hardware flow control, you should always select this in the UI of your driver by means of: Control Panel / System / Hardware / Device Manager / Ports (expand to see available ports) / COMx (double click on wanted port). If the driver recognizes the setting of SerialPort, it is then necessary to select "RequestToSend" in SerialPort to avoid that the original driver setting (hardware flow control) is changed. However, in that case you actually get an undesirable double flow control - one in hardware and one in software. If the driver does not recognize the setting, it is therefore better to set the flow control of SerialPort to "None" so that you only have the hardware control left.


 
 

SerialPort Events

After setting the buffer sizes, SerialPort fires up a background thread, which waits for something interesting to happen on the serial port by means of the API function WaitCommEvent(). What is interesting is set by means of the API function SetCommEvent, which is called with a 9-bit mask. The possible events are:

  1. Bit 0 = LSB. EV_RXCHAR. A character was received and placed in the input buffer.
  2. Bit 1. EV_RXFLAG. The event character was received and placed in the input buffer.
  3. Bit 2. EV_TXEMPTY. The last character in the output buffer was sent.
  4. Bit 3. EV_CTS. The CTS (clear-to-send) signal changed state.
  5. Bit 4. EV_DSR. The DSR (data-set-ready) signal changed state.
  6. Bit 5. EV_RLSD. The RLSD (receive-line-signal-detect) or CD (carrier-detect) signal changed state.
  7. Bit 6. EV_BREAK. A break was detected on input.
  8. Bit 7. EV_ERR. A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
  9. Bit 8. EV_RING. A ring indicator was detected.

SerialPort converts EV_RXCHAR to the DataReceived event, EV_CTS, EV_DSR, EV_RLSD and EV_BREAK to the PinChanged event and EV_ERR to the ErrorReceived event. Unfortunately, it does not use the EV_TXEMPTY event probably because it cannot be guaranteed that EV_TXEMPTY is raised when the serial output register in the UART is empty or just when the UART driver buffer before the FIFO is empty. Therefore, it is very difficult to control the modem signals and terminate a telegram with a break condition :-(

In case of a DataReceived, ErrorReceived or PinChanged event, the background thread calls the API function QueueUserWorkItem(), which graps a thread from the thread pool for any further processing of that event. If no thread is available in the pool, the event is put in queue for a thread (therefore Queue...). The background thread then immediately calls WaitCommEvent() again to wait for the next interesting event. WaitCommEvents clears the event flags before it returns (of course it doesn't clear the return value), so when WaitCommEvents is called again, the new call will not return before the UART driver has set one or more of the event flags again.

The new thread from the thread pool does the following:

  1. First it waits to get a lock on the underlying SerialStream object of SerialPort (Monitor.Enter(serialStreamObject)). The primary reason for this was to coordinate events with closing the serial port so that no events would be raised while closing was in progress, but the lock also ensures that DataReceived, PinChanged or ErrorReceived will not fire again before the event handler returns. This ensures that there will only be one instance of the event handler and it simplifies programming. Note that there is a mutual blocking between all events so that if for example a thread for DataReceived is running, PinChanged and ErrorReceived are also blocked until the thread is returned to the pool.

  2. In case of a DataReceived event, the thread then checks if the number of characters in the receive buffer is greater than or equal to the threshold level ReceivedBytesThreshold or if the received characters is &H1A. In these two cases, it calls your event handler for the DataReceived event. Because it is called from this (thread pool) thread, the event handler will also use this thread. The event handler is therefore not allowed to call methods of a control on the UI thread except for the four thread safe methods Invoke, BeginInvoke, EndInvoke and CreateGraphics and hiden methods to set simple properties like the background color.

    Note that &H1A = Ctrl Z is regarded as a End Of File (EOF) character. If you don't want your event handler for the DataReceived event to do anything special in case of &H1A, but just want to wait until the ReceivedBytesThreshold level is reached, you can for example put the following test into it:

       If (e.EventType = SerialData.chars) then
           ' Do whatever you need to do
       End If
       

    The SerialData enumeration has only two members - Eof, which is the &H1A character, and chars, which stands for all other characters.

  3. When the event handler returns, the lock is released and the thread is returned to the pool. This also happens if the number of received bytes was not greater than or equal to the threshold level (in case of DataReceived), but the background thread will just raise DataReceived again when one or more bytes are received (EV_RXCHAR set again), so the test will be performed again.

If you need to change the COM port, it is necessary with some time delay between the Close() and Open() commands to give the system some time to close down the background thread before a new background thread is made for the new COM port. The necessary amount of time is not specified, but 200 mS is usually enough. Since the UI thread has higher priority than the background thread and thread pool threads it is necessary to stop the UI thread temporary. The easiest way to do this is to call Sleep like for example Sleep(200). To ensure that the UI is updated before the UI thread goes to sleep, you can call Application.DoEvents() before the Sleep statement to empty the message queue.

It is important that you close the port before you terminate your application. If this is not done, the garbage collector of .NET may close the port while it is still in use!

A very common beginners mistake is to open and close the port for each transmission, but because of the big overhead and the necessary time delay this is not a good idea. Open the port when you have set the various properties and don't close it again before you want to change the COM port or close your application!


 
 

SerialPort Receive

The first thing you should be aware of when designing your SerialPort application is that both control.BeginInvoke and Control.Invoke are heavy stuff and should be limited as much as possible - not just because of the huge overhead with build of TME objects, but also because the processing of these type of messages has higher priority than other messages so that too many of these calls may block the UI.

Because most (all) practical applications are based on a communication protocol, which combines a number of bytes into a telegram and makes it possible to separate the various telegrams and detect errors, it is highly recommended to read the entire telegram in the event handler for the DataReceived event before you send it to the UI thread. In this way, you will limit the number of Invoke or BeginInvoke calls and will utilize the multithreading and not block the UI thread while you wait for bytes, perform error detection and reject any erroneous telegrams (telegram reception done on a thread pool thread). If you use ASCII and a very simple protocol where each telegram is just terminated with a given character - usually CR or LF, you can just call ReadLine. However, because of the lock in SerialPort you should be aware that you cannot close the port if the event handler for the DataReceived event doesn't exit (Close will hang). You should be aware of this if you use blocking calls like ReadLine or ReadTo(Value) or call ReadByte in a loop without checking BytesToRead. In these cases, you should always specify a timeout for data reception (ReadTimeout) to ensure that your handler always exits. Note that the reason for the ReadTimeout is to be able to close the port - not to make a receive timeout! This is a common misunderstanding. You cannot use ReadTimeout as a timeout together with the DataReceived event because until the first byte is received, the event handler is not running and therefore no receive method is called so the timeout is not active.

The default termination character for ReadLine is LF (Line Feed), but it is easy to change by means of the NewLine property. If you for example wants to change it to CR (Carrige Return) you can use the following code:

   COMPort.NewLine = Chr(13)
   ...
   COMPort.ReadLine
   
You can also use:
   COMPort.ReadTo(Chr(13))
   

Behind the scenes, ReadLine and ReadTo are in fact the same method, but if you change the NewLine property it will also affect WriteLine, which appends the NewLine character to the end of the transmitted string. Note that ReadLine and ReadTo strip off the NewLine character before the string is returned!

Note that Hayes modem commands (AT commands) are terminated with CR instead of LF.

When a full telegram has been received, you can send it to a method on the UI thread by means of control.BeginInvoke or control.Invoke. The are pro's and contras of both methods:

  • Control.BeginInvoke is usually the recommended method for beginners because it is simple and it has the advantage that the event handler for the DataReceived event can start working on the next telegram while the job on the UI thread is executing. However, because the priority of the UI thread is higher then the thread pool thread, this advantage is not so big in practice and in case of many telegrams there is a possibility for an overload where the thread pool has to generate more threads and more messages are posted to the message queue than the UI thread can handle. A further disadvantage is that since BeginInvoke returns immediately, it is necessary to transfer all data/arguments by value (ByVal) or else there is a chance that they may be overwritten by a new call of the event handler before they are used although the higher UI thread priority will usually prevent this, but not always (low priority threads also gets a chance of running).

  • Control.Invoke avoids the overload problem, but is a little more complicated to use than control.BeginInvoke because of a deadlock. If you try to close the port from the UI thread while the event handler is updating the GUI, the port will not close and the application freezes. The reason for this is that YourCOMPort.Close() is a blocking call, which waits for events to finish executing before it closes the port, but the events cannot finish before the Invoke operation is finished (event handler terminated) and this cannot happen while the UI thread hangs in the Close() call. A workaround for this is therefore to close the port from a different thread (not the UI thread) so that Close() does not block for the execution of the GUI update (or to use BeginInvoke, which does not block the event handler). You can for example close the port from a new thread like this:
       Imports System.Threading
    
       Dim t As New Thread(AddressOf ClosePort)
       t.Start()
    
       Private Sub ClosePort()
           If COMPort.IsOpen Then COMPort.Close()
       End Sub
       

    The thread will be closed by the OS as soon as the job is done and therefore not leak resources. Behind the scenes, VB builds a ThreadStart delegate for you (with the address of ClosePort), which is then passed to the thread constructor. In C#, you don't need the keyword AddressOf.

    When you call control.Invoke, you will lock the UI thread and the thread pool thread together so that the DataReceived event cannot fire again before the job is done. As this prevents the received telegram from being overwritten, you do not need to transfer it ByVal in a delegate, but can just use the standard delegate MethodInvoker to "wake" the method on the UI thread, which can then use the telegram buffer directly (global declaration). This saves a lot of time and byte copying!

    When the job on the UI thread is done and you return from the Invoke call, the event handler for the DataReceived event may terminate if no bytes have been received in the meantime or immediately read more bytes from the receive buffer if telegrams have been received (do the Invoke call within the BytesToRead loop). This saves some overhead. In case of heavy communication, you may have received several telegrams, but depending on your system, you may be able to throw some telegrams away or even all telegrams but the last one. It is anyway much more efficient to buffer the received bytes up in the receive buffer than to put them into TME objects and post messages to the message queue.

    With control.Invoke you can also choose to call the read method from the UI thread and simply use the Invoke call to "wake" the method to do so. If you try that with BeginInvoke, the event handler for the DataReceived event will usually return before the UI job has brought the number of bytes below the threshold level, and if one or more bytes has arrived (EV_RXCHAR set again), the event handler will be activated again and immediately invoke the UI method once more. This may go on at a very fast rate (oscillation) until the number of bytes gets below the threshold level or no more bytes are received. Especially at medium speed levels, this may result in dozens of unnecessary messages on the message queue. In tests where all events were numbered, we have seen approximately 20. Note that if you receive from the UI thread and use ReadLine or ReadTo(value), you will block the entire UI thread until the telegram has been received! The same thing may happen if you use ReadByte and don't check BytesToRead, so in practice this way can only be used together with ReadExisting, a "While YourCOMPort.BytesToRead > 0" loop or a "Do - Loop Until YourCOMPort.BytesToRead = 0" loop (more efficient than a While loop).

Previous versions of the sample program (up to February 19th 2010) used control.BeginInvoke for simplicity, but due to the higher efficiency of control.Invoke in case of heavy load conditions, the newer versions use that.

WARNING! SerialPort seems to have a bug, which courses it to fill up the receive buffer with 00 bytes. You can trigger this bug by means of a wrong baud rate, but unfortunately it has not been possible to find an exact trigger sequence.


 
 

Read Method

Because the help files does not describe how Read actually works, it is logical to assume that the method:

   BytesReceived = YourCOMPort.Read(buffer, offset, count)
   

is a blocking method, which does not return before "count" number of bytes are received. It is not!. If there are bytes available on the serial port, Read returns up to "count" bytes, but will not block (wait) for the remaining bytes. If there are no bytes available on the serial port, Read will block until at least one byte is available on the port, up until the ReadTimeout milliseconds have elapsed, at which time a TimeoutException will be thrown.


 
 

Program Description

The sample program has been designed for very high-speed operation with maximum efficiency as Max-i goes up to 1.843 Mbit/s and is able to transfer up to 10,000 telegrams per second where several hundreds may be passed to the PC. Because of this, not everything is made quite by-the-book. For example, the program uses global buffers, which are usually not recommended for object oriented programming. For low speed operation, you may instead define a delegate to transfer the received telegram to the UI thread and use BeginInvoke instead of Invoke.

The statement "Dim WithEvents COMPort As New SerialPort" makes a new instance of System.IO.Ports.SerialPort. Note that because the namespace System.IO.Ports is imported in the beginning of the program it is only necessary to specify "SerialPort" (instead of System.IO.Ports.SerialPort).

The Receiver subroutine is activated when the SerialPort object receives some data. Note that this activation is only possible because "WithEvent" has been specified in the object definition. The purpose of the Receiver subroutine is to collect a data package and then send this package to the Display subroutine for display. The type of data package depends on the application. In this case, a package is just all available bytes converted to a text string. For applications where each data package is terminated with a specified character, the whole Receiver subroutine may be replaced with:

   Dim Buffer As String

   Private Sub Receiver(ByVal sender As Object, Byval e As SerialDataReceivedEventArgs)_
           Handles COMPort.DataReceived
       Buffer = COMPort.readLine
       Me.Invoke(New MethodInvoker(AddressOf Display))
   End Sub
   

Or in case you prefer BeginInvoke and a delegate:

   Public Delegate Sub StringSubPointer(ByVal Buffer As String)

   Private Sub Receiver(ByVal sender As Object, Byval e As SerialDataReceivedEventArgs)_
           Handles COMPort.DataReceived
        Me.BeginInvoke(New StringSubPointer(AddressOf Display), COMPort.Readline)
   End Sub
   

Because you will use a blocking call (ReadLine) in this case, remember to specify a read timeout to ensure that you always are able to close the port:

   COMPort.ReadTimeout = 5000  ' 5 sec. timeout
   

To avoid repetitive string operations, the Receiver subroutine receives all bytes in an array and then converts the data to a string by means of the "Dim RxString As New String(RxArray, 0, I)" statement. This is the reason for not using the automatic hex to string conversion of VB, which only works on strings, which then have to be added together. Besides, the used look-up table method is approximately 43 times faster than the build-in String.Format method! This is worth thinking about! Don't use standard function if you can replace them with simple constructions like this!

Because the Receiver subroutine handles COMPort.DataReceived, it uses a thread pool thread, so Receiver is not allowed to display the data itself (write to the Received TextBox) because this would course an illegal cross thread call. Therefore, we use Invoke to marshal the call to the UI thread.

The Display subroutine just appends the received text to any content of the Received TextBox. For other applications like SCADA systems it may be desirable to overwrite the data instead by means of "Received.Text = (...)" instead of "Received.AppendText(...)" . Display is event driven, but since it do not ends with a "Handles xxx", it needs an Invoke or BeginInvoke statement to tell when to start.

The Transmitter subroutine transmits the content of the Transmitted TextBox when the Send button is pressed (handles SendButton.Click) and it appends "TX" plus a line feed to the Received TextBox. It primary consists of a state-action machine to handle the combined HEX and ASCII input and the quotation marks. If the port is not open, an error message is generated instead of the transmission. The transmitter has a 2 sec. timeout in case of troubles with the flow control (handshake).

The subroutine PortSelection is activated in case of a change in the selected COM port. The routine closes any open port, empties the message queue, waits 0.2S and then opens the new port. The Application.DoEvents() call is necessary to update the ComboBox and the modem control "lamps" before the UI thread goes to sleep.

The MaxiTesterLoad subroutine is activated when the form is loaded. It detects all available COM ports and loads them into a ComboBox. It then loads the mostly used baud rates, the possible number of data bits and the parity and flow control settings into other ComboBoxes. It also switches the modem control "lamps" off.

The MaxiTesterClosing subroutine first shows a dialog box to ensure that the program is not closed by accident. If closing is confermed, it closes any open port before the application terminates. Note that to prevent the deadlock mentioned in chapter "SerialPort Receive", the port is closed on a new thread.

The ClearReceivedText subroutine clears the Received TextBox, SaveText saves the content into a file and SendBreak generates a break condition.

The subroutines BaudRateSelection, DataBitsSelection, ParitySelection and SoftwareFlowControlSelection are used to select the speed, number of data bits, parity and flow control (handshake). Unfortunately, VB does not have a FromString (corresponding to ToString) method to convert from text to enumeration (integer) therefore it is necessary with a rather complicated type conversion.

The subroutine ModemLamps updates the modem control signal "lamps". Note that since we just change a simple property - the background color, it can be done from a thread pool thread without coursing an illegal cross thread error.


 
 

Download

Click here to download the source code.
Unzip MaxiCOM.zip and open MaxiCOM.vbproj. Form1.vb is the main code and Form1.Designer.vb contains parameters for all used objects. This download also includes MaxiCOM.exe in the [Bin][Release] folder.

Click here to download MaxiCOM.exe alone.

Note that the file structure of a VB program is extremely confusing, because the word 'Project' is used for two different things, and two directories by default have the same name. When you generate a new project you get the following default directory structure (only the most important files are shown):

   \Visual Studio 2005\
       Projects\
           YourProject\
               YourProject.sln
               YourProject\
                   Form1.vb                 ' Source file
                   Form1.Designer.vb        ' Designer source file
                   Form1.resx               ' .NET Resource file
                   YourProject.vbproj
                   YourProject.vbproj.user
                   bin\
                       debug\
                           YourProject.exe  ' Exe file for debugger (may also be published)
                       release\
                           YourProject.exe  ' Exe file to be included in the solution
   

However, the directory should actually look like this:

   \Visual Studio 2005\
       Solutions\                           ' Note Solutions - not Projects
           Solution1\
               Solution1.sln
               Project1\
                   Form1.vb                 ' Source file
                   Form1.Designer.vb        ' Designer source file
                   Form1.resx               ' .NET Resource file
                   Project1.vbproj          ' Project definition file
                   Project1.vbproj.user
                   bin\
                       debug\
                           Project1.exe     ' Exe file for debugger (may also be published)
                       release\
                           Project1.exe     ' Exe file to be included in the solution
               Project2\                    ' Any second project included in the solution
                   Form1.vb
                   Form1.Designer.vb
                   Form1.resx
                   Project2.vbproj
                   Project2.vbproj.user
                   bin\
                       debug\
                           Project2.exe
                       release\
                           Project2.exe
           Solution2\
               Solution2.sln
               Project3\
                   Form1.vb
                   Form1.Designer.vb
                   Form1.resx
                   Project3.vbproj
                   Project3.vbproj.user
                   bin\
                       debug\
                           Project3.exe 
                       release\
                           Project3.exe
               Project4\
                   Form1.vb
                   Form1.Designer.vb
                   Form1.resx
                   Project4.vbproj
                   Project4.vbproj.user
                   bin\
                       debug\
                           Project4.exe
                       release\
                           Project4.exe
   

A so-called VB project is really a superior solution, which may contain many projects, so the Projects folder does not contain projects, but solutions.

If you have any comments or suggestions, please let us know.