Click here to Skip to main content
15,891,375 members
Articles / Desktop Programming / ATL

Pluggable Components using Component Categories - Part II

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
18 Sep 20037 min read 82.7K   1.1K   46  
An article on using component categories to create pluggable components
// Serial.cpp: implementation of the CSerial class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Serial.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

#pragma warning (disable : 4127)

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CSerial::CSerial() : 
					m_pParent(NULL),
					m_heStop(NULL), 
					m_heSerialEvent(NULL),
					m_hThread(NULL),
					m_lLastError(ERROR_SUCCESS), 
					m_hPort(NULL), 
					m_eEvent(EEVENT_NONE), 
					m_heOverlapped(NULL),
					m_lpfunc(NULL)
{

}

CSerial::~CSerial()
{
	if (NULL != m_hThread)
	{
		_RPTF0(_CRT_WARN, "CSerial::~CSerial():\tSerial port not closed, closing explicitly\n");
		Close();
	}
}

CSerial::Availability CSerial::CheckPort(LPCTSTR lpszDevice)
{
	Availability Ret = EAVAILABILITY_AVAILABLE;
	HANDLE hPort = ::CreateFile(lpszDevice, GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

	if (INVALID_HANDLE_VALUE == hPort)
	{
		switch(::GetLastError())
		{
		case ERROR_FILE_NOT_FOUND:	// no such port
			Ret = EAVAILABILITY_NOTAVAILABLE;
			break;
		case ERROR_ACCESS_DENIED:	// in use
			Ret = EAVAILABILITY_INUSE;
			break;
		default:
			Ret = EAVAILABILITY_UNKNOWN;
			break;
		}
	}
	else
	{
		::CloseHandle(hPort);
	}

	return Ret;
}

LONG CSerial::Open(LPCTSTR lpszDevice, void* pParent, LPSERIALFUNC lpSer, DWORD dwInQueue, DWORD dwOutQueue)
{
	m_lLastError = ERROR_SUCCESS;

	ASSERT(NULL != pParent);
	ASSERT(NULL != lpSer);

	if (NULL != m_hPort)
	{
		m_lLastError = ERROR_ALREADY_INITIALIZED;
		_RPTF0(_CRT_WARN, "CSerial::Open():\tPort already Opened\n");
		return m_lLastError;
	}

	// Add FILE_SHARE_READ | FILE_SHARE_WRITE to allow for multiple threads to access com port (must use mutex)
	m_hPort = ::CreateFile(lpszDevice, GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
	if (INVALID_HANDLE_VALUE == m_hPort)
	{
		m_hPort = NULL;
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::Open():\tUnable to open port\n");
		return m_lLastError;
	}

	ASSERT(NULL == m_heOverlapped);

	m_heOverlapped = ::CreateEvent(NULL, TRUE, FALSE, NULL);
	if (NULL == m_heOverlapped)
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::Open():\tUnable to create event\n");
		::CloseHandle(m_hPort);
		m_hPort = NULL;
		return m_lLastError;
	}

	if (FALSE == ::SetupComm(m_hPort, dwInQueue, dwOutQueue))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::Open():\tUnable to setup the Serial port\n");
		Close();
		return m_lLastError;
	}

	SetEventMask();

	SetBaudRate(EBAUDRATE_9600);
	SetParity(EPARITY_NONE);
	SetDataBits(EDATABITS_8);
	SetStopBits(ESTOPBITS_1);
	SetHandshaking(EHANDSHAKE_OFF);

	SetReadTimeout(ETIMEOUT_NONBLOCKING);

	m_heStop = ::CreateEvent(NULL, TRUE, FALSE, NULL);
	m_heSerialEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

	if (NULL == m_heStop || NULL == m_heSerialEvent)
	{
		LONG lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::Open():\tUnable to create events\n");
		Close();
		m_lLastError = lLastError;
		return m_lLastError;
	}

	DWORD dwThreadID = 0;
	m_hThread = ::CreateThread(NULL, NULL, ThreadProc, (LPVOID)this, CREATE_SUSPENDED, &dwThreadID);
	if (NULL == m_hThread)
	{
		LONG lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::Open():\tUnable to start Serial thread\n");
		::CloseHandle(m_heStop);
		m_heStop = NULL;
		::CloseHandle(m_heSerialEvent);
		m_heSerialEvent = NULL;
		Close();
		m_lLastError = lLastError;
		return m_lLastError;
	}

	m_pParent = pParent;
	m_lpfunc = lpSer;
	
	::ResumeThread(m_hThread);

	return m_lLastError;
}

LONG CSerial::Close()
{
	if (NULL != m_hThread)
	{
		::SetEvent(m_heStop);
		::WaitForSingleObject(m_hThread, INFINITE);
		::CloseHandle(m_hThread);
		::CloseHandle(m_heStop);
		::CloseHandle(m_heSerialEvent);
		m_hThread = NULL;
		m_heStop = NULL;
		m_heSerialEvent = NULL;
	}

	m_pParent = NULL;
	m_lpfunc = NULL;
	
	m_lLastError = ERROR_SUCCESS;
	if (NULL == m_hPort)
	{
		_RPTF0(_CRT_WARN, "CSerial::Close():\tDon't need to close when device is not open\n");
		return m_lLastError;
	}

	::CloseHandle(m_heOverlapped);
	m_heOverlapped = NULL;

	::CloseHandle(m_hPort);
	m_hPort = NULL;

	return m_lLastError;
}

LONG CSerial::SetBaudRate(BaudRate Baud)
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::SetBaudRate():\tDevice not open\n");
		return m_lLastError;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);
	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetBaudRate():\tUnable to open COM state information\n");
		return m_lLastError;
	}

	switch (Baud)
	{
	case EBAUDRATE_110:
	case EBAUDRATE_300:
	case EBAUDRATE_600:
	case EBAUDRATE_1200:
	case EBAUDRATE_2400:
	case EBAUDRATE_4800:
	case EBAUDRATE_9600:
	case EBAUDRATE_14400:
	case EBAUDRATE_19200:
	case EBAUDRATE_38400:
	case EBAUDRATE_56000:
	case EBAUDRATE_57600:
	case EBAUDRATE_115200:
	case EBAUDRATE_128000:
	case EBAUDRATE_256000:
		dcb.BaudRate = (DWORD)Baud;
		break;
	default:
		dcb.BaudRate = (DWORD)EBAUDRATE_38400;
		break;
	}
	
	if (FALSE == ::SetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetBaudRate():\tUnable to set COM state information\n");
		return m_lLastError;
	}

	return m_lLastError;
}

LONG CSerial::SetParity(Parity Par)
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::SetParity():\tDevice not open\n");
		return m_lLastError;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);
	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetParity():\tUnable to open COM state information\n");
		return m_lLastError;
	}

	switch (Par)
	{
	case EPARITY_NONE:
	case EPARITY_EVEN:
	case EPARITY_ODD:
	case EPARITY_SPACE:
	case EPARITY_MARK:
		dcb.Parity = (BYTE)Par;
		dcb.fParity = (Par != EPARITY_NONE);
		break;
	default:
		dcb.Parity = (BYTE)EPARITY_NONE;
		dcb.fParity = FALSE;
		break;
	}
	
	if (FALSE == ::SetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetParity():\tUnable to set COM state information\n");
		return m_lLastError;
	}

	return m_lLastError;
}

LONG CSerial::SetDataBits(DataBits Data)
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::SetDataBits():\tDevice not open\n");
		return m_lLastError;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);
	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetDataBits():\tUnable to open COM state information\n");
		return m_lLastError;
	}

	switch (Data)
	{
	case EDATABITS_5:
	case EDATABITS_6:
	case EDATABITS_7:
	case EDATABITS_8:
		dcb.ByteSize = (BYTE)Data;
		break;
	default:
		dcb.ByteSize = (BYTE)EDATABITS_8;
		break;
	}
		
	if (FALSE == ::SetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetDataBits():\tUnable to set COM state information\n");
		return m_lLastError;
	}

	return m_lLastError;
}

LONG CSerial::SetStopBits(StopBits Stop)
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::SetStopBits():\tDevice not open\n");
		return m_lLastError;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);
	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetStopBits():\tUnable to open COM state information\n");
		return m_lLastError;
	}

	switch (Stop)
	{
	case ESTOPBITS_1:
	case ESTOPBITS_1_5:
	case ESTOPBITS_2:
		dcb.StopBits = (BYTE)Stop;
		break;
	default:
		dcb.StopBits = (BYTE)ESTOPBITS_1;
		break;
	}
		
	if (FALSE == ::SetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetStopBits():\tUnable to set COM state information\n");
		return m_lLastError;
	}

	return m_lLastError;
}

LONG CSerial::SetHandshaking(HandShaking Hand)
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::SetHandshaking():\tDevice not open\n");
		return m_lLastError;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);
	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetHandshaking():\tUnable to open COM state information\n");
		return m_lLastError;
	}

	switch (Hand)
	{
	case EHANDSHAKE_OFF:
		dcb.fOutxCtsFlow = FALSE;
		dcb.fOutxDsrFlow = FALSE;
		dcb.fDtrControl = DTR_CONTROL_DISABLE;
		dcb.fOutX = FALSE;
		dcb.fInX = FALSE;
		dcb.fRtsControl = RTS_CONTROL_DISABLE;
		break;
	case EHANDSHAKE_SOFTWARE:
		dcb.fOutxCtsFlow = TRUE;
		dcb.fOutxDsrFlow = TRUE;
		dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
		dcb.fOutX = FALSE;
		dcb.fInX = FALSE;
		dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
		break;
	case EHANDSHAKE_HARDWARE:
		dcb.fOutxCtsFlow = FALSE;
		dcb.fOutxDsrFlow = FALSE;
		dcb.fDtrControl = DTR_CONTROL_DISABLE;
		dcb.fOutX = TRUE;
		dcb.fInX = TRUE;
		dcb.fRtsControl = RTS_CONTROL_DISABLE;
		break;
	default:	// by default, turn it off
		dcb.fOutxCtsFlow = FALSE;
		dcb.fOutxDsrFlow = FALSE;
		dcb.fDtrControl = DTR_CONTROL_DISABLE;
		dcb.fOutX = FALSE;
		dcb.fInX = FALSE;
		dcb.fRtsControl = RTS_CONTROL_DISABLE;
		break;
//		ASSERT(!"Invalid Handshaking Mode");
//		m_lLastError = E_INVALIDARG;
//		return m_lLastError;
	}
	
	if (FALSE == ::SetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetHandshaking():\tUnable to set COM state information\n");
		return m_lLastError;
	}

	return m_lLastError;
}

LONG CSerial::SetEventChar(BYTE ch, BOOL bAdjustMask)
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::SetStopBits():\tDevice not open\n");
		return m_lLastError;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);
	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetStopBits():\tUnable to open COM state information\n");
		return m_lLastError;
	}

	dcb.EvtChar = (char)ch;

	if (TRUE == bAdjustMask)
	{
		SetEventMask(GetEventMask() | EEVENT_RECVEV);
	}
	
	if (FALSE == ::SetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetStopBits():\tUnable to set COM state information\n");
		return m_lLastError;
	}

	return m_lLastError;
}

LONG CSerial::SetEventMask(DWORD dwMask)
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::SetEventMask():\tDevice is not open\n");
		return m_lLastError;
	}

	if (FALSE == ::SetCommMask(m_hPort, dwMask))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetEventMask():\tUnable to set event mask\n");
		return m_lLastError;
	}
	return m_lLastError;
}

LONG CSerial::SetReadTimeout(Timeout Time)
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::SetReadTimeout():\tDevice is not open\n");
		return m_lLastError;
	}

	COMMTIMEOUTS cto;
	if (FALSE == ::GetCommTimeouts(m_hPort, &cto))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetReadTimeout():\tUnable to read timeout information\n");
		return m_lLastError;
	}

	switch (Time)
	{
	case ETIMEOUT_BLOCKING:
		cto.ReadIntervalTimeout = 0;
		cto.ReadTotalTimeoutConstant = 0;
		cto.ReadTotalTimeoutMultiplier = 0;
		break;
	case ETIMEOUT_NONBLOCKING:
		cto.ReadIntervalTimeout = MAXDWORD;
		cto.ReadTotalTimeoutConstant = 0;
		cto.ReadTotalTimeoutMultiplier = 0;
		break;
	default:
		ASSERT(!"Invalid Timeout Mode");
		m_lLastError = E_INVALIDARG;
		return m_lLastError;
	}

	if (FALSE == ::SetCommTimeouts(m_hPort, &cto))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::SetReadTimeout():\tUnable to set timeout information\n");
		return m_lLastError;
	}
	return m_lLastError;
}

CSerial::BaudRate CSerial::GetBaudRate()
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::GetBaudRate():\tDevice is not open\n");
		return EBAUDRATE_UNKNOWN;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);

	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetBaudRate():\tUnable to read DCB information\n");
		return EBAUDRATE_UNKNOWN;
	}

	return (BaudRate)dcb.BaudRate;
}

CSerial::Parity CSerial::GetParity()
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::GetParity():\tDevice is not open\n");
		return EPARITY_UNKNOWN;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);

	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetParity():\tUnable to read DCB information\n");
		return EPARITY_UNKNOWN;
	}

	if (FALSE == dcb.fParity)
	{
		return EPARITY_NONE;
	}

	return (Parity)dcb.Parity;
}

CSerial::DataBits CSerial::GetDataBits()
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::GetDataBits():\tDevice is not open\n");
		return EDATABITS_UNKNOWN;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);

	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetDataBits():\tUnable to read DCB information\n");
		return EDATABITS_UNKNOWN;
	}

	return (DataBits)dcb.ByteSize;
}

CSerial::StopBits CSerial::GetStopBits()
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::GetStopBits():\tDevice is not open\n");
		return ESTOPBITS_UNKNOWN;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);

	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetStopBits():\tUnable to read DCB information\n");
		return ESTOPBITS_UNKNOWN;
	}

	return (StopBits)dcb.ByteSize;
}

CSerial::HandShaking CSerial::GetHandshaking()
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::GetHandShaking():\tDevice is not open\n");
		return EHANDSHAKE_UNKNOWN;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);

	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetHandShaking():\tUnable to read DCB information\n");
		return EHANDSHAKE_UNKNOWN;
	}

	if (DTR_CONTROL_HANDSHAKE == dcb.fDtrControl && RTS_CONTROL_HANDSHAKE == dcb.fRtsControl)
		return EHANDSHAKE_HARDWARE;

	if (TRUE == dcb.fInX && TRUE == dcb.fOutX)
		return EHANDSHAKE_SOFTWARE;

	return EHANDSHAKE_OFF;
}

BYTE CSerial::GetEventChar()
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::GetEventChar():\tDevice is not open\n");
		return NULL;
	}

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);

	if (FALSE == ::GetCommState(m_hPort, &dcb))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetEventChar():\tUnable to read DCB information\n");
		return NULL;
	}

	return (BYTE)dcb.EvtChar;
}

DWORD CSerial::GetEventMask()
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::GetEventMask():\tDevice is not open\n");
		return NULL;
	}

	DWORD dwMask;

	if (FALSE == ::GetCommMask(m_hPort, &dwMask))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetEventMask():\tUnable to read mask information\n");
		return NULL;
	}

	return dwMask;
}

LONG CSerial::Write(const void* pData, DWORD dwSize, DWORD* pdwWrote, LPOVERLAPPED lpOverlapped, DWORD dwTimeout)
{
	ASSERT((NULL == lpOverlapped || NULL != pdwWrote) || !"Overlapped Operation should specify the Wrote variable"); 
	m_lLastError = ERROR_SUCCESS;
	DWORD dwWrote = 0;
	if (NULL == pdwWrote)
		pdwWrote = &dwWrote;

	*pdwWrote = 0;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;

		_RPTF0(_CRT_WARN, "CSerial::Write():\tDevice is not open\n");
		return m_lLastError;
	}

	OVERLAPPED ovl;
	if (NULL == lpOverlapped)
	{
		memset(&ovl, 0, sizeof(OVERLAPPED));
		ovl.hEvent = m_heOverlapped;
		lpOverlapped = &ovl;
	}

	ASSERT(HasOverlappedIoCompleted(lpOverlapped) || !"Overlapped Operation in progress");

	if (FALSE == ::WriteFile(m_hPort, pData, dwSize, pdwWrote, lpOverlapped))
	{
		LONG lLastError = ::GetLastError();

		if (ERROR_IO_PENDING != lLastError)
		{
			m_lLastError = lLastError;
			_RPTF0(_CRT_WARN, "CSerial::Write():\tUnable to write data\n");
			return m_lLastError;
		}

		if (&ovl == lpOverlapped)
		{
			switch(::WaitForSingleObject(lpOverlapped->hEvent, dwTimeout))
			{
			case WAIT_OBJECT_0:
				if (FALSE == ::GetOverlappedResult(m_hPort, lpOverlapped, pdwWrote, FALSE))
				{
					m_lLastError = ::GetLastError();
					_RPTF0(_CRT_WARN, "CSerial::Write():\tOverlapped completed with no results\n");
					return m_lLastError;
				}
				break;
			case WAIT_TIMEOUT:
				::CancelIo(m_hPort);
				m_lLastError = ERROR_TIMEOUT;
				return m_lLastError;
			default:
				m_lLastError = ::GetLastError();
				_RPTF0(_CRT_WARN, "CSerial::Write():\tUnable to wait for data to be sent\n");
				return m_lLastError;
			}
		}
	}
	else
	{
		::SetEvent(lpOverlapped->hEvent);
	}
	return m_lLastError;
}

LONG CSerial::Read(void* pData, DWORD dwSize, DWORD* pdwRead, LPOVERLAPPED lpOverlapped, DWORD dwTimeout)
{
	ASSERT((NULL == lpOverlapped || NULL != pdwRead) || !"Overlapped Operation should specify the Read variable"); 
	m_lLastError = ERROR_SUCCESS;
	DWORD dwRead = 0;
	if (NULL == pdwRead)
		pdwRead = &dwRead;

	*pdwRead = 0;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;

		_RPTF0(_CRT_WARN, "CSerial::Read():\tDevice is not open\n");
		return m_lLastError;
	}

	OVERLAPPED ovl;
	if (NULL == lpOverlapped)
	{
		memset(&ovl, 0, sizeof(ovl));
		ovl.hEvent = m_heOverlapped;
		lpOverlapped = &ovl;
	}

	ASSERT(HasOverlappedIoCompleted(lpOverlapped) || !"Overlapped Operation in progress");

#ifdef _DEBUG
	memset(pData, 0xDC, dwSize);	// help to find buffer overrun problems
#endif // _DEBUG

	if (FALSE == ::ReadFile(m_hPort, pData, dwSize, pdwRead, lpOverlapped))
	{
		LONG lLastError = ::GetLastError();

		if (ERROR_IO_PENDING != lLastError)
		{
			m_lLastError = lLastError;
			_RPTF0(_CRT_WARN, "CSerial::Read():\tUnable to read from device\n");
			return m_lLastError;
		}

		if (&ovl == lpOverlapped)
		{
			switch(::WaitForSingleObject(lpOverlapped->hEvent, dwTimeout))
			{
			case WAIT_OBJECT_0:
				if (FALSE == ::GetOverlappedResult(m_hPort, lpOverlapped, pdwRead, FALSE))
				{
					m_lLastError = ::GetLastError();
					_RPTF0(_CRT_WARN, "CSerial::Read():\tOverlapped Operation completed with no results\n");
					return m_lLastError;
				}
				break;
			case WAIT_TIMEOUT:
				::CancelIo(m_hPort);
				m_lLastError = ERROR_TIMEOUT;
				return m_lLastError;
			default:
				m_lLastError = ::GetLastError();
				_RPTF0(_CRT_WARN, "CSerial::Read():\tUnable to wait for data to be read\n");
				return m_lLastError;
			}
		}
	}
	else
	{
		::SetEvent(lpOverlapped->hEvent);
	}
	return m_lLastError;
}

HANDLE CSerial::GetHandle()
{
	return m_hPort;
}

BOOL CSerial::IsOpen()
{
	return (NULL != m_hPort);
}

LONG CSerial::GetLastError() const
{
	return m_lLastError;
}

BOOL CSerial::GetCTS()
{
	m_lLastError = ERROR_SUCCESS;
	
	DWORD dwModemStatus = 0L;
	if (FALSE == ::GetCommModemStatus(m_hPort, &dwModemStatus))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetCTS():\tUnable to read modem status\n");
		return FALSE;
	}
	return 0 != (dwModemStatus & MS_CTS_ON);
}

BOOL CSerial::GetDSR()
{
	m_lLastError = ERROR_SUCCESS;
	
	DWORD dwModemStatus = 0L;
	if (FALSE == ::GetCommModemStatus(m_hPort, &dwModemStatus))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetDSR():\tUnable to read modem status\n");
		return FALSE;
	}
	return 0 != (dwModemStatus & MS_DSR_ON);
}

BOOL CSerial::GetRing()
{
	m_lLastError = ERROR_SUCCESS;
	
	DWORD dwModemStatus = 0L;
	if (FALSE == ::GetCommModemStatus(m_hPort, &dwModemStatus))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetRing():\tUnable to read modem status\n");
		return FALSE;
	}
	return 0 != (dwModemStatus & MS_RING_ON);
}

BOOL CSerial::GetRLSD()
{
	m_lLastError = ERROR_SUCCESS;
	
	DWORD dwModemStatus = 0L;
	if (FALSE == ::GetCommModemStatus(m_hPort, &dwModemStatus))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetRLSD():\tUnable to read modem status\n");
		return FALSE;
	}
	return 0 != (dwModemStatus & MS_RLSD_ON);
}

LONG CSerial::Purge()
{
	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::Purge():\tDevice is not open\n");
		return m_lLastError;
	}

	if (FALSE == ::PurgeComm(m_hPort, PURGE_RXCLEAR | PURGE_TXCLEAR))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::Purge():\tPurge failed\n");
	}
	return m_lLastError;
}

LONG CSerial::WaitEvent(LPOVERLAPPED lpOverlapped, DWORD dwTimeout)
{
	m_lLastError = ERROR_SUCCESS;

	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::WaitEvent():\tDevice is not open\n");
		return m_lLastError;
	}

	OVERLAPPED ovl;
	if (NULL == lpOverlapped)
	{
		memset(&ovl, 0, sizeof(OVERLAPPED));
		ovl.hEvent = m_heOverlapped;
		lpOverlapped = &ovl;
	}

	ASSERT(HasOverlappedIoCompleted(lpOverlapped) || !"Overlapped Operation in progress");

	if (FALSE == ::WaitCommEvent(m_hPort, (LPDWORD)&m_eEvent, lpOverlapped))
	{
		LONG lLastError = ::GetLastError();
		if (ERROR_IO_PENDING != lLastError)
		{
			m_lLastError = lLastError;
			_RPTF0(_CRT_WARN, "CSerial::WaitEvent():\tUnable to wait for Serial event\n");
			return m_lLastError;
		}

		if (&ovl == lpOverlapped)
		{
			switch(::WaitForSingleObject(lpOverlapped->hEvent, dwTimeout))
			{
			case WAIT_OBJECT_0:
				break;
			case WAIT_TIMEOUT:
				::CancelIo(m_hPort);
				m_lLastError = ERROR_TIMEOUT;
				return m_lLastError;
			default:
				m_lLastError = ::GetLastError();
				_RPTF0(_CRT_WARN, "CSerial::WaitEvent():\tUnable to wait for Serial event to arrive\n");
				return m_lLastError;
			}
		}
	}
	else
	{
		::SetEvent(lpOverlapped->hEvent);
	}
	return m_lLastError;
}

CSerial::Event CSerial::GetLastEvent()
{
	Event event = m_eEvent;
	m_eEvent = EEVENT_NONE;
	return event;
}

CSerial::Error CSerial::GetLastSerialError()
{
	m_lLastError = ERROR_SUCCESS;
	if (NULL == m_hPort)
	{
		m_lLastError = ERROR_INVALID_HANDLE;
		_RPTF0(_CRT_WARN, "CSerial::GetLastSerialError():\tDevice is not open\n");
		return EERROR_UNKNOWN;
	}
	DWORD dwErrors = 0;

	if (FALSE == ::ClearCommError(m_hPort, &dwErrors, 0))
	{
		m_lLastError = ::GetLastError();
		_RPTF0(_CRT_WARN, "CSerial::GetLastSerialError():\tUnable to read Serial status\n");
		return EERROR_UNKNOWN;
	}

	return (Error)dwErrors;
}

DWORD WINAPI CSerial::ThreadProc(LPVOID lpParam)
{
	CSerial* pThis = reinterpret_cast<CSerial*>(lpParam);
	return pThis->ThreadProc();
}

DWORD CSerial::ThreadProc()
{
	OVERLAPPED ovl = { 0 };
	ovl.hEvent = m_heSerialEvent;

	HANDLE handles[2];
	handles[0] = m_heStop;
	handles[1] = ovl.hEvent;

	if (ERROR_SUCCESS != WaitEvent(&ovl))
		return m_lLastError;

	BOOL bStop = FALSE;
	while (FALSE == bStop)
	{
		switch(::WaitForMultipleObjects(sizeof(handles)/sizeof(HANDLE), handles, FALSE, INFINITE))
		{
		case WAIT_OBJECT_0:
			{
				bStop = TRUE;
				break;
			}
		case WAIT_OBJECT_0 + 1:
			{
				Event event = GetLastEvent();
				DWORD dwErrors = 0;
				if (FALSE == ::ClearCommError(m_hPort, &dwErrors, 0))
				{
					m_lLastError = ::GetLastError();
					_RPTF0(_CRT_WARN, "CSerial::ThreadProc():\tUnable to clear errors\n");
				}

				Error err = (Error)dwErrors;

				if (0 != event)
				{
//					::PostMessage(m_hParent, g_iSerialMessage, (WPARAM)event, (LPARAM)err);
					m_lpfunc((WPARAM)m_pParent, (WPARAM)event, (LPARAM)err);
				}

				if (ERROR_SUCCESS != WaitEvent(&ovl))
				{
					return m_lLastError;
				}
				break;
			}
		default:	// something went wrong!
			{
				bStop = TRUE;
				break;
			}
		}
	}
	return 0;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
I started programming at 15 with a TI-82 calclator in Z80 assembly (oh, those were the days . . .) I am pretty much a self taught programmer. I've taught myself Visual Basic, C/C++, Java, and am currently working on C#. I also like to experiment with system administration and security issues, and occassionally I work on web design. For the last 4 years, I have worked for Leitch, Inc. as a Software Engineer and graduated from Old Dominion University with bachelor's degrees in Computer Science, Mathematics, and Business Management in December of 2004.

Comments and Discussions