Click here to Skip to main content
15,887,135 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
See more:
Hi, i like to make my class working with exceptions or if needed not working with exception. I like to make them switchable. I made a serial port class and it throws me exceptions as i need. But when i choose to not use them how can i get this elegantly. Currently i "if else" them via a member variable which will be set at the point of creating the object. But that's silly. Is there a better way of doing that?

Thanks

What I have tried:

C#
//header file
#pragma once

#define WIN32_LEAN_AND_MEAN             

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <cstdlib>
#include <iostream>
#include <assert.h>
#include <mutex>
#include <string>
#include <sstream>
#include <chrono>
#include <thread>#include <time.h>
#include <fstream>

#ifdef _DEBUG
#define pDebug(msg)	(std::cout << msg << std::endl)
#else
#define pDebug(msg)	//
#endif


#define	MAX_WAIT_TIME		3000
#define BUFFER_LENGTH		200

namespace serial
{

	class MSerialException : public std::exception
	{
		// Disable copy constructors
		MSerialException& operator=(const MSerialException&) = delete;
		std::string e_what_;
	public:
		MSerialException(std::string file,int line,const char* description) {
			std::stringstream ss;
			ss << "SerialException - " << description << " failed." << " file " << file << " line " << line;
			e_what_ = ss.str();
		}
		MSerialException(const MSerialException& other) : e_what_(other.e_what_) {}
		virtual ~MSerialException() {}
		virtual const char* what() const throw () {
			return e_what_.c_str();
		}
	};

	typedef struct  
	{
		DCB dcb;
		COMMTIMEOUTS timeouts;
	}PORT_DEFINITIONS;

	class MSerial
	{

	private:
		HANDLE m_fd;
		HANDLE m_hEvent;
		COMSTAT m_status;
		DWORD m_errors;

		const WCHAR* comport;
		bool m_connected;
		bool m_Overlapped;
		bool m_Thread;
		bool m_WithException;

		HANDLE	m_hThreadTerm;
		HANDLE	m_hThreadStarted;
		HANDLE	m_hDataRx;
		HANDLE	m_hThread;

		OVERLAPPED Overlapped;
		LPDWORD lpThreadId;

		// Mutex used to lock the read functions
		std::mutex m_read_mutex;
		// Mutex used to lock the write functions
		std::mutex m_write_mutex;

		//buffer for insoming data
		char *incoming_Buffer;
		std::mutex m_buffer_mutex;

	public:
		MSerial();
		MSerial(BOOL bOverlapped, BOOL bWithException);
		virtual ~MSerial();

		bool open(const CHAR* comport, PORT_DEFINITIONS	settings);
		bool open_async(const CHAR* comport, PORT_DEFINITIONS	settings);
		bool close();

		DWORD read(char* inc_msg);
		DWORD read(char* inc_msg,
			DWORD read_bytes_count
		);

		int write(const char* data_sent,
			DWORD data_sent_length
		);

		bool WriteEx(LPCVOID lpBuffer,
			DWORD dwNumberOfBytesToWrite,
			LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
		);

		bool ReadEx(LPVOID lpBuffer,
			DWORD dwNumberOfBytesToRead,
			LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
		);

		static unsigned __stdcall ThreadFn(void* pvParam);

		inline bool isOpen() const;
		inline void SetDataReadEvent() { SetEvent(m_hDataRx); }
		DWORD WaitForReadEvent(DWORD timeout) { return WaitForSingleObject(m_hDataRx, timeout); };

		DWORD available();
		void flush();
		void purge();

	};
}

//source file
#include "MSerial.h"

#define THROW(message) (throw MSerialException(__FILE__, __LINE__, message))

inline std::string _prefix_port_if_needed(const std::string& input)
{
	static std::string windows_com_port_prefix = "\\\\.\\";
	if (input.compare(0, windows_com_port_prefix.size(), windows_com_port_prefix) != 0)
	{
		return windows_com_port_prefix + input;
	}
	return input;
}

using namespace serial;

MSerial::MSerial()
{
	//initialize to zero
	m_errors = 0;
	memset(&m_status, 0, sizeof(COMSTAT));
	memset(&Overlapped, 0, sizeof(OVERLAPPED));
	m_Overlapped = false;
	m_connected = false;
	m_Thread = false;
	m_WithException = false;
	m_fd = INVALID_HANDLE_VALUE;
	m_hEvent = INVALID_HANDLE_VALUE;
	m_hThreadTerm = INVALID_HANDLE_VALUE;
	m_hThreadStarted = INVALID_HANDLE_VALUE;
	m_hDataRx = INVALID_HANDLE_VALUE;
	m_hThread = INVALID_HANDLE_VALUE;

	comport = NULL;
	incoming_Buffer = NULL;
	lpThreadId = NULL;

	std::string msg = "constructor for MSerial";
	pDebug(msg);
}

MSerial::MSerial(BOOL bOverlapped = false, BOOL bWithException = false)
	: m_Overlapped(bOverlapped), m_WithException(bWithException)
{
	//initialize to zero
	m_errors = 0;
	memset(&m_status,0,sizeof(COMSTAT));
	memset(&Overlapped, 0, sizeof(OVERLAPPED));
	m_connected = false;
	m_Thread = false;
	m_fd = INVALID_HANDLE_VALUE;
	m_hEvent = INVALID_HANDLE_VALUE;
	m_hThreadTerm = INVALID_HANDLE_VALUE; 
	m_hThreadStarted = INVALID_HANDLE_VALUE;
	m_hDataRx = INVALID_HANDLE_VALUE;
	m_hThread = INVALID_HANDLE_VALUE;

	comport = NULL;
	incoming_Buffer = NULL;
	lpThreadId = NULL;

	std::string msg = "constructor for MSerial";
	pDebug(msg);
}

MSerial::~MSerial()
{
	/* cancel all io operations now */
	CancelIo(m_fd);

	if (isOpen())
		close();

	if (m_Thread)
	{
		//delete memory 
		delete incoming_Buffer;
	}

	std::string msg = "destructor for MSerial";
	pDebug(msg);
}

bool MSerial::open(const CHAR* comport, PORT_DEFINITIONS setting)
{
	bool ret = false;

	if (isOpen())
		if (m_WithException)
			THROW("Comport already opened.");
		else
			return false;

	if (comport == NULL)
		if (m_WithException)
			THROW("No comport specified.");
		else
			return false;


	if (setting.dcb.BaudRate == 0)
		if (m_WithException)
			THROW("No Baud rate specified.");
		else
			return false;
		

	std::string port_with_prefix = _prefix_port_if_needed(comport);
	LPCSTR lp_port = port_with_prefix.c_str();

	m_fd = CreateFile(lp_port,
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		m_Overlapped ? FILE_FLAG_OVERLAPPED : FILE_ATTRIBUTE_NORMAL,
		NULL);


	if (m_fd == INVALID_HANDLE_VALUE)
	{
		std::stringstream ss;
		ss << "Error while opening serial port: " << GetLastError();
		if (m_WithException)
			THROW(ss.str().c_str());
		else
			return false;
		
	}
	else {

		// Flush away any bytes previously read or written.
		BOOL success = FlushFileBuffers(m_fd);
		if (!success)
		{
			std::stringstream ss;
			ss << "Failed to flush serial port: " << GetLastError();
			CloseHandle(m_fd);
			if (m_WithException)
				THROW(ss.str().c_str());
			else
				return false;
			
		}

		DCB dcb = { 0 };
		COMMTIMEOUTS timeouts = { 0 };

		dcb.DCBlength = sizeof(DCB);

		if (GetCommState(m_fd, &dcb) && GetCommTimeouts(m_fd, &timeouts))
		{

			dcb.BaudRate = setting.dcb.BaudRate;
			dcb.ByteSize = setting.dcb.ByteSize;
			dcb.Parity = setting.dcb.Parity;
			dcb.StopBits = setting.dcb.StopBits;
			dcb.fDtrControl = setting.dcb.fDtrControl;
			dcb.fRtsControl = setting.dcb.fRtsControl;

			dcb.fDsrSensitivity = 0;
			dcb.fOutxDsrFlow = 0;

			if (!SetCommState(m_fd, &dcb))
			{				
				if (m_WithException)
					THROW("Error: could not set serial port params");
				else
					return false;

			}
			else 
				m_connected = true;
			
				
			/* check for overlapped mode */
			if (!m_Overlapped) {
				/* comport is not async so place some timeouts */
				timeouts.ReadIntervalTimeout = setting.timeouts.ReadIntervalTimeout;
				timeouts.ReadTotalTimeoutMultiplier = setting.timeouts.ReadTotalTimeoutMultiplier;
				timeouts.ReadTotalTimeoutConstant = setting.timeouts.ReadTotalTimeoutConstant;
				timeouts.WriteTotalTimeoutMultiplier = setting.timeouts.ReadTotalTimeoutMultiplier;
				timeouts.WriteTotalTimeoutConstant = setting.timeouts.WriteTotalTimeoutConstant;

				if (!SetCommTimeouts(m_fd, &timeouts))
				{
					if (m_WithException)
						THROW("Error: could not set serial port params");
					else
						return false;					
				}
				else 
					m_connected = true;
								
			}
			else {
				Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
				if (Overlapped.hEvent == NULL)
				{
					std::stringstream ss;
					ss << "Failed to CreateEvent Reason: " << GetLastError();
					if (m_WithException)
						THROW(ss.str().c_str());
					else
						return false;
					
				}			
			}
		}
		else {
			if (m_WithException)
				THROW("Error: Failed to get current serial port params");
			else
				return false;
		}

		// reset the communication port
		purge();
	}

	return m_connected;
}

bool MSerial::open_async(const CHAR* comport, PORT_DEFINITIONS setting)
{
	if (isOpen())
		if (m_WithException)
			THROW("Comport already opened.");
		else
			return false;


	if (comport == NULL)
		if (m_WithException)
			THROW("No comport specified.");
		else
			return false;

	if (setting.dcb.BaudRate == 0)
		if (m_WithException)
			THROW("No Baud rate specified.");
		else
			return false;


	if (!m_Overlapped)
		THROW("Not specified for overlapped.");

	if (open(comport, setting))
	{

		incoming_Buffer = new char[BUFFER_LENGTH];
		if (incoming_Buffer == NULL)
			THROW("acquire Buffer failed.");

		Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (Overlapped.hEvent == NULL)
		{
			std::stringstream ss;
			ss << "Failed to CreateEvent Reason: " << GetLastError();
			THROW(ss.str().c_str());
		}

		memset(incoming_Buffer, 0, BUFFER_LENGTH);

		m_hThreadTerm = CreateEvent(NULL, TRUE, FALSE, NULL);
		m_hThreadStarted = CreateEvent(NULL, TRUE, FALSE, NULL);

		if (m_hThreadTerm == INVALID_HANDLE_VALUE && !m_hThreadTerm)
			THROW("creation of Events failed.");

		if (m_hThreadStarted == INVALID_HANDLE_VALUE && !m_hThreadStarted)
			THROW("creation of Events failed.");

		m_hThread = (HANDLE)_beginthreadex(0, 0, MSerial::ThreadFn, (void*)this, 0, 0);

		DWORD dwWait = WaitForSingleObject(m_hThreadStarted, INFINITE);
		if (dwWait != WAIT_OBJECT_0)
			THROW("creation of Threads failed.");

		m_connected = true;
	}

	return m_connected;
}

DWORD MSerial::read(char* inc_msg)
{
	if (!isOpen())
		THROW("MSerial not connected");

	std::lock_guard<std::mutex> lock(m_read_mutex);

	DWORD bytes_read = 0;

	ClearCommError(m_fd, &m_errors, &m_status);

	if (m_status.cbInQue > 0) {
		if (!ReadFile(m_fd, inc_msg, m_status.cbInQue, &bytes_read, NULL)) {
			std::stringstream ss;
			ss << "Error while reading from the serial port: " << GetLastError();
			THROW(ss.str().c_str());
		}
	}
	return m_status.cbInQue;
}

DWORD MSerial::read(char* inc_msg, DWORD read_bytes_count)
{
	if (!isOpen())
		THROW("MSerial not connected");

	if (read_bytes_count == 0)
		return read_bytes_count;

	std::lock_guard<std::mutex> lock(m_read_mutex);

	DWORD bytes_read = 0;

	ClearCommError(m_fd, &m_errors, &m_status);

	if (m_status.cbInQue > 0) {
		if (!ReadFile(m_fd, inc_msg, read_bytes_count, &bytes_read, NULL)) {
			std::stringstream ss;
			ss << "Error while reading from the serial port: " << GetLastError();
			THROW(ss.str().c_str());
		}
	}
	return bytes_read;
}

int MSerial::write(const char* data_sent,DWORD data_sent_length)
{
	if (!isOpen())
		THROW("MSerial not connected");

	if (data_sent_length == 0)
		return data_sent_length;

	std::lock_guard<std::mutex> lock(m_write_mutex);

	DWORD bytes_sent;

	if (!WriteFile(m_fd, (void*)data_sent, data_sent_length, &bytes_sent, NULL)) {
		ClearCommError(m_fd, &m_errors, &m_status);
		return bytes_sent;
	}
	else
		return bytes_sent;
}

bool MSerial::WriteEx(LPCVOID lpBuffer, DWORD dwNumberOfBytesToWrite,LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
{
	if (!m_Overlapped)
		THROW("Not specified as overlapped");

	if (lpCompletionRoutine == NULL)
		THROW("No CompletionRoutine specified");

	std::lock_guard<std::mutex> lock(m_write_mutex);

	return WriteFileEx(m_fd,lpBuffer,dwNumberOfBytesToWrite,&Overlapped,lpCompletionRoutine);
}

bool MSerial::ReadEx(LPVOID lpBuffer, DWORD dwNumberOfBytesToRead, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
{
	if (!m_Overlapped)
		THROW("Not specified as overlapped");

	if (lpCompletionRoutine == NULL)
		THROW("No CompletionRoutine specified");

	std::lock_guard<std::mutex> lock(m_read_mutex);

	return ReadFileEx(m_fd, lpBuffer, dwNumberOfBytesToRead, &Overlapped, lpCompletionRoutine);
}

bool MSerial::close()
{
	/* discard all oustanding io operations and clear all buffers*/
	purge();

	if (m_connected)
	{
		if (m_Thread)
		{
			SignalObjectAndWait(m_hThreadTerm, m_hThread, INFINITE, FALSE);
		}

		CloseHandle(m_hThreadTerm);
		CloseHandle(m_hThreadStarted);
		CloseHandle(m_hDataRx);
		CloseHandle(m_hThread);
		m_connected = false;
		CloseHandle(m_fd);
		return true;
	}
	else
		return false;
}

bool MSerial::isOpen() const
{
	return m_connected;
}

void MSerial::flush()
{
	if (!isOpen()) {
		THROW("MSerial::flush()");
	}
	FlushFileBuffers(m_fd);
}

void MSerial::purge()
{
	if (!isOpen())
		THROW("MSerial::purge()");

		if (!PurgeComm(m_fd, PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR))
		{
			std::stringstream ss;
			ss << "Error while reseting serial port: " << GetLastError();
			THROW(ss.str().c_str());
		}
}

DWORD MSerial::available()
{
	if (!isOpen())
		THROW("MSerial not connected");

	COMSTAT cs;
	if (!ClearCommError(m_fd, NULL, &cs)) {
		std::stringstream ss;
		ss << "Error while checking status of the serial port: " << GetLastError();
		THROW(ss.str().c_str());
	}
	return cs.cbInQue;
}

unsigned __stdcall MSerial::ThreadFn(void* pvParam)
{

	MSerial* apThis = (MSerial*)pvParam;
	bool abContinue = true;
	DWORD dwEventMask = 0;

	OVERLAPPED ov;
	memset(&ov, 0, sizeof(ov));
	ov.hEvent = CreateEvent(0, true, 0, 0);
	HANDLE arHandles[2];
	arHandles[0] = apThis->m_hThreadTerm;

	if (ov.hEvent == NULL)
	{
		std::stringstream ss;
		ss << "Error while overlapped event: " << GetLastError();
		THROW(ss.str().c_str());
	}

	//now set criterium for generating event
	if (!SetCommMask(apThis->m_fd, EV_RXCHAR | EV_TXEMPTY))
	{
		std::stringstream ss;
		ss << "Failed to SetCommMask Reason: " << GetLastError();
		THROW(ss.str().c_str());
	}

	apThis->m_hDataRx = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (apThis->m_hDataRx == NULL)
	{
		std::stringstream ss;
		ss << "Failed to CreateEvent Reason: " << GetLastError();
		THROW(ss.str().c_str());
	}

	DWORD dwWait;
	SetEvent(apThis->m_hThreadStarted);
	while (abContinue)
	{

		BOOL abRet = WaitCommEvent(apThis->m_fd, &dwEventMask, &ov);
		if (!abRet)
		{
			if (GetLastError() != ERROR_IO_PENDING)
			{
				std::stringstream ss;
				ss << "Error while waiting for comm event: " << GetLastError();
				THROW(ss.str().c_str());
			}
		}

		arHandles[1] = ov.hEvent;

		dwWait = WaitForMultipleObjects(2, arHandles, FALSE, INFINITE);
		switch (dwWait)
		{
		case WAIT_OBJECT_0:
		{
			_endthreadex(1);
		}
		break;
		case WAIT_OBJECT_0 + 1:
		{
			DWORD dwMask;
			if (GetCommMask(apThis->m_fd, &dwMask))
			{
				//write IO operation finished
				if (dwMask == EV_TXEMPTY)
				{
					ResetEvent(ov.hEvent);
					continue;
				}

			}

			//read data here...
			int iAccum = 0;

			std::lock_guard<std::mutex> lock(apThis->m_buffer_mutex);

			try
			{
				std::string szDebug;
				BOOL abRet = false;

				DWORD dwBytesRead = 0;
				OVERLAPPED ovRead = { 0 };
				memset(&ovRead, 0, sizeof(ovRead));
				ovRead.hEvent = CreateEvent(0, true, 0, 0);

				if (ovRead.hEvent == NULL)
				{
					std::stringstream ss;
					ss << "Error while overlapped event: " << GetLastError();
					THROW(ss.str().c_str());
				}

				do
				{
					ResetEvent(ovRead.hEvent);
					char szTmp[1];
					int iSize = sizeof(szTmp);
					memset(szTmp, 0, sizeof szTmp);
					abRet = ReadFile(apThis->m_fd, szTmp, sizeof(szTmp), &dwBytesRead, &ovRead);
					if (!abRet)
					{
						abContinue = FALSE;
						break;
					}
					if (dwBytesRead > 0)
					{
						apThis->incoming_Buffer[iAccum] = *szTmp;
						iAccum += dwBytesRead;
					}
				} while (0);// dwBytesRead > 0 );
				CloseHandle(ovRead.hEvent);
			}
			catch (...)
			{
				std::stringstream ss;
				ss << "ThreadFn catched fire";
				THROW(ss.str().c_str());
			}

			if (iAccum > 0)
			{
				apThis->SetDataReadEvent();
			}
			ResetEvent(ov.hEvent);
		}
		break;
		}//switch
	}
	return 0;
}
Posted
Updated 25-Nov-23 13:16pm
v2
Comments
CHill60 25-Nov-23 19:17pm    
You really have not stated your problem clearly I'm afraid. You have dumped a lot of code here without explaining what your problem is
Richard MacCutchan 26-Nov-23 3:21am    
You should use proper C++ style std::exception - cppreference.com[^]. And if you want them to be switchable then write your own derived class.
0x01AA 26-Nov-23 10:58am    
One easy way (maybe not the cleanest) is to implement a method DoException(string message) which throws then an exception based on the value of m_WithException. At least this way you get rid of all the 'if' in the code.

1 solution

You might try to use my error code objects. They are a cross between exceptions and error codes. See C++ Error Handling with Error Code Objects[^]. Latest version of code can be downloaded from GitHub - neacsum/mlib: Multi-Purpose Library[^]
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900