📄 comm.cpp
字号:
{
TRACE0("Read thread not exiting. Terminating it.\n");
// ::TerminateThread(m_hReadThread, 0);
::TerminateThread(m_pReadThread->m_hThread, 0);
// The ReadThread cleans up these itself if it terminates normally.
// ::CloseHandle(m_hReadThread);
// m_hReadThread = 0;
// m_dwReadThreadID = 0;
}
delete m_pReadThread;
m_pReadThread = NULL;
}
}
// Closes the write thread by signaling the CloseEvent.
// Purges any outstanding writes on the comm port.
//
// Note that terminating a thread leaks memory (read the docs).
// Besides the normal leak incurred, there is an event object
// that doesn't get closed. This isn't worth worrying about
// since it shouldn't happen anyway.
void CCommInfo::CloseWriteThread(void)
{
// If it exists...
// if (m_hWriteThread)
if (m_pWriteThread)
{
TRACE0("Closing Write Thread\n");
// Signal the event to close the worker threads.
::SetEvent(m_hCloseEvent);
// Purge all outstanding writes.
::PurgeComm(m_hCommFile, PURGE_TXABORT | PURGE_TXCLEAR);
// Wait 10 seconds for it to exit. Shouldn't happen.
// if (::WaitForSingleObject(m_hWriteThread, 10000) == WAIT_TIMEOUT)
if (::WaitForSingleObject(m_pWriteThread->m_hThread, 10000) == WAIT_TIMEOUT)
{
TRACE0("Write thread not exiting. Terminating it.\n");
// ::TerminateThread(m_hWriteThread, 0);
::TerminateThread(m_pWriteThread->m_hThread, 0);
// The WriteThread cleans up these itself if it terminates normally.
// ::CloseHandle(m_hWriteThread);
// m_hWriteThread = 0;
// m_dwWriteThreadID = 0;
}
delete m_pWriteThread;
m_pWriteThread = NULL;
}
}
// This is the starting point for the Read Thread.
// The Read Thread uses overlapped ReadFile and sends any strings
// read from the comm port to the UI to be printed. This is
// eventually done through a PostMessage so that the Read Thread
// is never away from the comm port very long. This also provides
// natural desynchronization between the Read thread and the UI.
//
// If the CloseEvent object is signaled, the Read Thread exits.
//
// Separating the Read and Write threads is natural for a application
// like this sample where there is no need for synchronization between
// reading and writing. However, if there is such a need (for example,
// most file transfer algorithms synchronize the reading and writing),
// then it would make a lot more sense to have a single thread to handle
// both reading and writing.
DWORD CCommInfo::ReadThreadProc(LPVOID lpvParam)
{
CCommInfo* pComm = (CCommInfo*)lpvParam;
if (pComm == NULL ||
!pComm->IsKindOf(RUNTIME_CLASS(CCommInfo)))
return -1; // illegal parameter
// Needed for overlapped I/O (ReadFile)
OVERLAPPED overlappedRead = {0, 0, 0, 0, NULL};
// Needed for overlapped Comm Event handling.
OVERLAPPED overlappedCommEvent = {0, 0, 0, 0, NULL};
// Lets put an event in the Read overlapped structure.
overlappedRead.hEvent = ::CreateEvent(
NULL, // no security
TRUE, // explicit reset req
TRUE, // initial event set
NULL); // no name
if (overlappedRead.hEvent == NULL)
{
TRACE0("Unable to CreateEvent. \n");
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
}
// ?????
if (!::SetCommMask(pComm->m_hCommFile, EV_RXCHAR))
{
TRACE0("Unable to SetCommMask: EV_ERR\n");
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
}
// ?????
// And an event for the CommEvent overlapped structure.
overlappedCommEvent.hEvent = CreateEvent(
NULL, // no security
TRUE, // explicit reset req
TRUE, // initial event set
NULL); // no name
if (overlappedCommEvent.hEvent == NULL)
{
TRACE0("Unable to CreateEvent. \n");
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
}
// We will be waiting on these objects.
HANDLE HandlesToWaitFor[3];
HandlesToWaitFor[0] = pComm->m_hCloseEvent;
HandlesToWaitFor[1] = overlappedCommEvent.hEvent;
HandlesToWaitFor[2] = overlappedRead.hEvent;
// Setup CommEvent handling.
// Set the comm mask so we receive error signals.
if (!::SetCommMask(pComm->m_hCommFile, EV_ERR))
{
TRACE0("Unable to SetCommMask: EV_ERR\n");
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
}
// Start waiting for CommEvents (Errors)
DWORD fdwEvtMask;
if (!pComm->SetupWaitCommEvent(&overlappedCommEvent, &fdwEvtMask))
{
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
}
// Start waiting for Read events.
DWORD nNumberOfBytesRead;
if (!pComm->SetupReadEvent(&overlappedRead,
&nNumberOfBytesRead))
{
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
}
// Keep looping until we break out.
while (TRUE)
{
// Wait until some event occurs (data to read; error; stopping).
DWORD dwHandleSignaled =
::WaitForMultipleObjects(3, HandlesToWaitFor,
FALSE, INFINITE);
// Which event occured?
switch (dwHandleSignaled)
{
case WAIT_OBJECT_0: // Signal to end the thread.
// Time to exit.
goto LABEL_ENDREADTHREAD;
case WAIT_OBJECT_0 + 1: // CommEvent signaled.
// Handle the CommEvent.
if (!pComm->HandleCommEvent(&overlappedCommEvent, &fdwEvtMask, TRUE))
{
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
}
// Start waiting for the next CommEvent.
if (!pComm->SetupWaitCommEvent(&overlappedCommEvent, &fdwEvtMask))
{
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
}
break;
case WAIT_OBJECT_0 + 2: // Read Event signaled.
// Get the new data!
if (!pComm->HandleReadEvent(&overlappedRead,
&nNumberOfBytesRead))
{
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
}
// Wait for more new data.
if (!pComm->SetupReadEvent(&overlappedRead,
&nNumberOfBytesRead))
{
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
}
break;
case WAIT_FAILED: // Wait failed. Shouldn't happen.
TRACE1("Read WAIT_FAILED %lu: \n", ::GetLastError());
{
LPVOID lpMsgBuf;
::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
::GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL);
// Display the string.
TRACE0((LPCTSTR)lpMsgBuf);
TRACE0("\n");
// Free the buffer.
::LocalFree(lpMsgBuf);
}
pComm->PostCommError();
break;
// goto LABEL_ENDREADTHREAD;
default: // This case should never occur.
TRACE1("Unexpected Wait return value '%lx'", dwHandleSignaled);
pComm->PostCommError();
goto LABEL_ENDREADTHREAD;
} // End of switch(dwHandleSignaled).
} // End of while(TRUE) loop.
// Time to clean up Read Thread.
LABEL_ENDREADTHREAD:
TRACE0("Read thread shutting down\n");
::PurgeComm(pComm->m_hCommFile, PURGE_RXABORT | PURGE_RXCLEAR);
if (overlappedRead.hEvent)
::CloseHandle(overlappedRead.hEvent);
if (overlappedCommEvent.hEvent)
::CloseHandle(overlappedCommEvent.hEvent);
// pComm->m_pReadThread = NULL;
::AfxEndThread(0);
// pComm->m_dwReadThreadID = 0;
// ::CloseHandle(pComm->m_hReadThread);
// pComm->m_hReadThread = 0;
return 0;
}
// Handle an outstanding Comm Event.
// (if bRetrieveEvent == TRUE) retrieves an outstanding CommEvent and
// deals with it. The only event that should occur is an EV_ERR event,
// signalling that there has been an error on the comm port.
//
// Normally, comm errors would not be put into the normal data stream
// as this sample is demonstrating. Putting it in a status bar would
// be more appropriate for a real application.
BOOL CCommInfo::HandleCommEvent(LPOVERLAPPED lpOverlappedCommEvent,
LPDWORD lpfdwEvtMask, BOOL bRetrieveEvent)
{
// If this fails, it could be because the file was closed (and I/O is
// finished) or because the overlapped I/O is still in progress. In
// either case (or any others) its a bug and return FALSE.
if (bRetrieveEvent)
{
DWORD dwDummy;
if (!::GetOverlappedResult(m_hCommFile, lpOverlappedCommEvent, &dwDummy, FALSE))
{
switch (::GetLastError())
{
// Its possible for this error to occur if the
// service provider has closed the port. Time to end.
case ERROR_INVALID_HANDLE:
TRACE0("ERROR_INVALID_HANDLE, Likely that the Service Provider has closed the port.\n");
return FALSE;
default:
TRACE0("Unexpected GetOverlappedResult for WaitCommEvent: \n");
return FALSE;
}
}
}
// Was the event an error?
if (*lpfdwEvtMask & EV_ERR)
{
// Which error was it?
DWORD dwErrors;
if (!::ClearCommError(m_hCommFile, &dwErrors, NULL))
{
switch (::GetLastError())
{
// Its possible for this error to occur if the
// service provider has closed the port. Time to end.
case ERROR_INVALID_HANDLE:
TRACE0("ERROR_INVALID_HANDLE, Likely that the Service Provider has closed the port.\n");
return FALSE;
default:
TRACE0("ClearCommError: \n");
return FALSE;
}
}
// Its possible that multiple errors occured and were handled
// in the last ClearCommError. Because all errors were signaled
// individually, but cleared all at once, pending comm events
// can yield EV_ERR while dwErrors equals 0. Ignore this event.
TCHAR szError[128] = "";
if (dwErrors != 0)
{
if (dwErrors & CE_FRAME)
{
if (szError[0])
strcat(szError," and ");
strcat(szError,"CE_FRAME");
}
if (dwErrors & CE_OVERRUN)
{
if (szError[0])
strcat(szError," and ");
strcat(szError,"CE_OVERRUN");
}
if (dwErrors & CE_RXPARITY)
{
if (szError[0])
strcat(szError," and ");
strcat(szError,"CE_RXPARITY");
}
if (dwErrors & ~(CE_FRAME | CE_OVERRUN | CE_RXPARITY))
{
if (szError[0])
strcat(szError," and ");
strcat(szError,"EV_ERR Unknown EvtMask");
}
}
else
{
strcat(szError, "NULL Error");
}
TRACE2("Comm Event: '%s', EvtMask = '%lx'\n", szError, dwErrors);
return TRUE;
}
// Should not have gotten here. Only interested in ERR conditions.
TRACE1("Unexpected comm event %lx\n", *lpfdwEvtMask);
return FALSE;
}
// Sets up the overlapped WaitCommEvent call.
// This function is a helper function for the Read Thread that sets up
// the WaitCommEvent so we can deal with comm events (like Comm errors)
// if they occur.
BOOL CCommInfo::SetupWaitCommEvent(LPOVERLAPPED lpOverlappedCommEvent,
LPDWORD lpfdwEvtMask)
{
//LABEL_STARTSETUPCOMMEVENT:
while (TRUE)
{
// Make sure the CloseEvent hasn't been signaled yet.
// Check is needed because this function is potentially recursive.
if (::WaitForSingleObject(m_hCloseEvent, 0) != WAIT_TIMEOUT)
return FALSE;
// Start waiting for Comm Errors.
if (::WaitCommEvent(m_hCommFile, lpfdwEvtMask, lpOverlappedCommEvent))
{
// This could happen if there was an error waiting on the
// comm port. Lets try and handle it.
TRACE0("Event (Error) waiting before WaitCommEvent.\n");
if (!HandleCommEvent(NULL, lpfdwEvtMask, FALSE))
return FALSE;
// What could cause infinite recursion at this point?
// goto LABEL_STARTSETUPCOMMEVENT;
}
else
break;
}
// We expect ERROR_IO_PENDING returned from WaitCommEvent
// because we are waiting with an overlapped structure.
switch (::GetLastError())
{
// LastError was ERROR_IO_PENDING, as expected.
case ERROR_IO_PENDING:
TRACE0("Waiting for a CommEvent (Error) to occur.\n");
return TRUE;
// Its possible for this error to occur if the
// service provider has closed the port. Time to end.
case ERROR_INVALID_HANDLE:
TRACE0("ERROR_INVALID_HANDLE, Likely that the Service Provider has closed the port.\n");
return FALSE;
// Unexpected error. No idea what could cause this to happen.
default:
TRACE0("Unexpected WaitCommEvent error !");
return FALSE;
}
}
// The starting point for the Write thread.
// The Write thread uses a PeekMessage loop to wait for a string to write,
// and when it gets one, it writes it to the Comm port. If the CloseEvent
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -