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\
(\
mySerialPort->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?
|