How to implement this in detail depends on the amount and rate of data to be send and received and the used protocol (e.g. acknowledgements, triggers).
But using one or more threads for the communication would be the best solution.
A possible implementation:
- Create a new class to implement the communication (no base class or
CObject
) - Add event handle member vars for stop and I/O detection
- Add an
Open
function that opens and configures the serial port in overlapped mode, creates the events and starts the the thread(s) - Add a
Close
function that signals the thread(s) to stop, and closes the serial port and the events - Add the worker thread function(s)
- Add this class as a member to your
CWinApp
or CMainFrame
derived class
Inside the worker thread create an
OVERLAPPED
struct using the I/O event handle and call
SetCommMask()
to specifiy the events. Inside the loop call
WaitCommEvent()
and use
WaitForMultipleObjects()
to detect the stop and overlapped I/O events. The time out value can be used to perform periodical actions (e.g. sending data).
To pass received data to your output window, send user defined messages (
WM_APP
+x) from the thread to the output window (the communication class should have a member var with the window's handle). If the data that should be passed would fit in the
LPARAM
and
WPARAM
parameters, pass them using these. Otherwise you must provide a special buffer for your data using locking (e.g. a GetData() function that returns a copy of the data and clears the buffer).
The output window must have a handler for the user defined message that updates the screen.
Example snippets (without error checking):
class CCom : public CObject
{
HANDLE m_hCom;
private:
CWinThread *m_pThread;
HANDLE m_hevThread;
CEvent m_evKill;
UINT WorkerThread(void);
static UINT CommThread(LPVOID pParam);
};
bool CCom::Open(LPCTSTR lpszPort)
{
m_hCom = ::CreateFile(lpszPort,
GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, NULL);
m_hevThread = ::CreateEvent(NULL, TRUE, FALSE, NULL);
m_pThread = AfxBeginThread(CommThread, this,
THREAD_PRIORITY_ABOVE_NORMAL, 0, CREATE_SUSPENDED);
m_evKill.ResetEvent();
m_pThread->m_bAutoDelete = FALSE;
m_pThread->ResumeThread();
return true;
}
void CCom::Close()
{
if (m_pThread)
{
if (m_pThread->m_hThread != INVALID_HANDLE_VALUE)
{
DWORD dwExitCode;
::GetExitCodeThread(m_pThread->m_hThread, &dwExitCode))
if (dwExitCode == STILL_ACTIVE)
{
m_evKill.SetEvent();
::WaitForSingleObject(m_pThread->m_hThread, INFINITE);
}
}
delete m_pThread;
m_pThread = NULL;
}
if (m_hCom != INVALID_HANDLE_VALUE)
{
::CloseHandle(m_hCom);
m_hCom = INVALID_HANDLE_VALUE;
}
if (m_hevThread)
{
::CloseHandle(m_hevThread);
m_hevThread = NULL;
}
}
UINT CCom::CommThread(LPVOID pParam)
{
CCom* pThis = reinterpret_cast<ccom*>(pParam);
return pThis->WorkerThread();
}
UINT CCom::WorkerThread(void)
{
bool bKill = false;
bool bPending = false;
DWORD dwEvent = 0;
HANDLE ahWait[2] = { m_evKill.m_hObject, m_hevThread };
OVERLAPPED ov = {0};
ov.hEvent = m_hevThread;
::SetCommMask(m_hCom, EV_RXCHAR);
while (!bKill)
{
DWORD nEvent = 0;
DWORD nDone = 0;
if (!bPending)
{
dwEvent = 0;
if (::WaitCommEvent(m_hCom, &dwEvent, &ov))
nEvent = dwEvent;
else
{
if (::GetLastError() == ERROR_IO_PENDING)
bPending = true;
else
break;
}
}
switch (::WaitForMultipleObjects(
bPending ? 2 : 1,
ahWait, FALSE,
bPending ? m_nWaitEventTO : 0))
{
case WAIT_OBJECT_0 :
bKill = true;
break;
case WAIT_OBJECT_0 + 1 :
bPending = false;
if (::GetOverlappedResult(m_hCom, &ov, &nDone, FALSE))
nEvent = dwEvent;
break;
case WAIT_TIMEOUT :
break;
}
if (nEvent & EV_RXCHAR)
{
}
}
::SetCommMask(m_hCom, 0);
return 0;
}