t e m p o r a l 
 d o o r w a y 

Reading the Serial Port From A Thread

 

Introduction

Many times it is desirable to read from the serial port in an asynchronous manner. This means using a thread so that the application can continue to operate while the thread is performing the I/O.

The following code is for just such a thread.

The Context

This thread class is an inner class for a serial port component. It expects the serial port to have been set up in advance. It accepts a pointer to the component, so that as the receipts are performed the component is updated. Indeed, the component offers a property called IncomingTransmission which, when set, triggers an event in the component that can be used to customize the reaction to a receipt.

The following #defines are used to aid readability:

#define DataPendingReceipt ErrorCode == ERROR_IO_PENDING

#define ReadRequested\
!ReadFile\
(\
m
ySerialPort->SerialPort,\
RawIncomingTransmission,\
sizeof(RawIncomingTransmission),\
&RawIncomingTransmissionLength,\
&ReadOverlapInformation\
)

The Code

  class PACKAGE ReadSerialThread: public TThread
   {
      private:

         HANDLE ReadEvent;
         OVERLAPPED ReadOverlapInformation;

         SerialPortComponent *mySerialPort;
         char RawIncomingTransmission[255];
         unsigned long RawIncomingTransmissionLength;

         void __fastcall ShowTransmission(void)
         {
             for (int Index = 0; Index < (int) RawIncomingTransmissionLength; Index++)
             {
               mySerialPort->RawIncomingTransmission[Index] = RawIncomingTransmission[Index];
             };
 
            mySerialPort->RawIncomingTransmissionLength = RawIncomingTransmissionLength;
            mySerialPort->IncomingTransmission = RawIncomingTransmission;
         };

         void __fastcall Execute(void)
         {
            while (!Terminated)
             {
               strnset(RawIncomingTransmission,'\0',sizeof(RawIncomingTransmission));

               DWORD Length; // Used for read

               if (ReadRequested)
               {
                   DWORD ErrorCode = GetLastError();

                   if (DataPendingReceipt)
                   {
                     WaitForSingleObjectEx(ReadEvent,(int) (mySerialPort->TimeOutInSeconds*1000),TRUE);
                     GetOverlappedResult(mySerialPort->SerialPort,&ReadOverlapInformation,&RawIncomingTransmissionLength,mySerialPort->BinaryMode /* Wait if binary */);

                     if (!Terminated)
                     {
                        Synchronize(ShowTransmission); // Even if nothing received
                     };
                   }
                   else
                   {
                     char ErrorMessage[255];

                     FormatMessage
                     (
                         FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                         NULL,
                         ErrorCode,
                         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                         (LPTSTR) &ErrorMessage,
                         0,
                         NULL
                     );

                      strcpy(RawIncomingTransmission,ErrorMessage);
                     Synchronize(ShowTransmission);
                   };
               };
             };
         };

      public:

         __fastcall ReadSerialThread(SerialPortComponent *theSerialPort): TThread(TRUE)
         {
            mySerialPort = theSerialPort;
            ReadEvent = CreateEvent(NULL,false,false,NULL);
            ReadOverlapInformation.hEvent = ReadEvent;
            FreeOnTerminate = true;
            Resume(); // Thread now runs
         };

         __fastcall ~ReadSerialThread(void)
         {
            CloseHandle(ReadEvent);
         };

         void __fastcall Terminate(void)
         {
            TThread::Terminate();
            SetEvent(ReadEvent);
         };
   };

The Thread Explained

The constructor and destructor are at the end of the class definition. The constructor simply retains the pointer to the calling component, sets up the read event using the Windows API CreateEvent call and sets FreeOnTerminate. Then the thread is started.

On destruction the event handle is closed.

The Terminate method calls the inherited method (which sets Terminated) and then sets the ReadEvent to allow the thread to see the termination request.

The Execute method is the meat of the process. As long as the thread has not been asked to terminate, it zeroes the raw buffer which it keeps internally, checks for a read pending, waits for the read event (which returns immediately if the data has arrived), and then uses GetOverlappedResult to read the raw (binary) data. Again, it checks for termination, and if it is not to terminate, it synchronizes (this is essential!) with the component (which is in the main thread) and provides the data to it.

How the Thread Is Started And Stopped

myReadSerialThread = new ReadSerialThread(this);

in the component starts the thread.

if (myReadSerialThread != NULL) myReadSerialThread->Terminate();

in the component terminates it.

Pretty simple, isn't it?

Copyright © 2004 by Mark Cashman (unless otherwise indicated), All Rights Reserved