// SerialDoc.cpp : implementation of the CSerialDoc class // #include "stdafx.h" #include "Serial.h" #include "SerialDoc.h" #include "MainFrame.h" #include "SerialView.h" #include "TimeoutDlg.h" #include "TransferDlg.h" #include "OXSCSTP.H" #include "OXSCFILE.H" #include "OXSCEXCP.H" #include "Globals.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif static TCHAR _szAllFilesFilter[] = _T("All Files (*.*)|*.*||"); typedef struct tagTHREADINFO { COXSerialCommFile* pCommDevice; HANDLE hEvent; UINT msgOutput; UINT msgBackspace; HWND hWnd; } THREADINFO, *PTHREADINFO, FAR* LPTHREADINFO; ///////////////////////////////////////////////////////////////////////////// // CSerialDoc IMPLEMENT_DYNCREATE(CSerialDoc, CDocument) BEGIN_MESSAGE_MAP(CSerialDoc, CDocument) //{{AFX_MSG_MAP(CSerialDoc) ON_COMMAND(IDM_CONNECT, OnConnect) ON_UPDATE_COMMAND_UI(IDM_CONNECT, OnUpdateConnect) ON_COMMAND(IDM_DISCONNECT, OnDisconnect) ON_UPDATE_COMMAND_UI(IDM_DISCONNECT, OnUpdateDisconnect) ON_COMMAND(IDM_RECEIVE, OnReceive) ON_UPDATE_COMMAND_UI(IDM_RECEIVE, OnUpdateReceive) ON_COMMAND(IDM_SEND, OnSend) ON_UPDATE_COMMAND_UI(IDM_SEND, OnUpdateSend) ON_COMMAND(IDM_SERIAL_SETUP, OnSerialSetup) ON_UPDATE_COMMAND_UI(IDM_SERIAL_SETUP, OnUpdateSerialSetup) ON_COMMAND(IDM_SET_TIMEOUT, OnSetTimeout) ON_UPDATE_COMMAND_UI(IDM_SET_TIMEOUT, OnUpdateSetTimeout) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CSerialDoc construction/destruction CSerialDoc::CSerialDoc() { m_pCommFile = NULL; m_pCommConfig = NULL; m_dwTxTimeout = m_dwRxTimeout = 200L; m_pIncomingTextThread = NULL; } CSerialDoc::~CSerialDoc() { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); if (m_pIncomingTextThread != NULL) { m_pIncomingTextThread->ResumeThread(); m_eventClose.SetEvent(); ::WaitForSingleObject(m_pIncomingTextThread->m_hThread, INFINITE); } if (m_pCommConfig != NULL) { delete m_pCommConfig; m_pCommConfig = NULL; } if (m_pCommFile != NULL) { delete m_pCommFile; m_pCommFile = NULL; } } ///////////////////////////////////////////////////////////////////////////// // CSerialDoc serialization void CSerialDoc::Serialize(CArchive& ar) { // CEditView contains an edit control which handles all serialization ((CEditView*) m_viewList.GetHead())->SerializeRaw(ar); } ///////////////////////////////////////////////////////////////////////////// // CSerialDoc diagnostics #ifdef _DEBUG void CSerialDoc::AssertValid() const { CDocument::AssertValid(); } void CSerialDoc::Dump(CDumpContext& dc) const { CDocument::Dump(dc); } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CSerialDoc helpers inline CSerialView* CSerialDoc::GetView() const { // Returns a pointer to the "Terminal" view ASSERT_VALID(this); ASSERT_VALID(AfxGetMainWnd()); POSITION pos = GetFirstViewPosition(); if (pos != NULL) { CSerialView* pView = (CSerialView*) GetNextView(pos); ASSERT_VALID(pView); ASSERT_KINDOF(CSerialView, pView); return pView; } ASSERT(FALSE); return NULL; } BOOL CSerialDoc::SendKeyDown(UINT nChar) { // If a connection is open SendKeyDown will send nChar to the receiving side's // "Terminal" view. If the connection is closed, nothing happens. // // if (!m_pCommFile->IsOpen()) return FALSE; if (nChar == VK_RETURN) { GetView()->Output(_T("\r\n")); m_pCommFile->Write(_T("\r\n"), sizeof(_T("\r\n"))); } else if (nChar == VK_BACK) { GetView()->Backspace(); m_pCommFile->Write(_T("\b"), sizeof(_T("\b"))); } else { GetView()->Output(_T("%c"), (char) nChar); m_pCommFile->Write(&nChar, sizeof(nChar)); } return TRUE; } ///////////////////////////////////////////////////////////////////////////// // Thread & Thread helper functions DWORD ReadEventBytesFromPort(COXSerialCommFile* pCommHandle, LPBYTE pbBuffer, DWORD dwLength) { // // pCommHandle - a pointer to a COXSerialCommFile object from which to receive // pbBuffer - buffer to receive incoming data // dwLength - size of pbBuffer // // // This function will determine if there is any data to be read on pCommHandle, if // there is it is read into pbBuffer. DWORD dwBytesToRead = 0; TRY { dwBytesToRead = pCommHandle->GetBytesToRead(); if (dwBytesToRead > dwLength) dwBytesToRead = dwLength; return pCommHandle->Read(pbBuffer, dwBytesToRead); } CATCH(COXSerialCommException, e) { TRACE(_T("There was an error trying to read from the COMM port.\n")); e->ReportError(MB_OK | MB_ICONSTOP); } END_CATCH return 0; } UINT IncomingTextThread(LPVOID pParam) { // // pParam - pointer to THREADINFO structure // // This thread will monitor incoming data. Any incoming data is sent to the // "Terminal" window as output text. // // This thread is suspended and resumed to minimize overhead. PTHREADINFO pInfo = NULL; pInfo = (PTHREADINFO) pParam; if (pInfo == NULL) return (UINT)-1; //////////////////////////////////////////////////////////////////////////// // Monitor incoming data BYTE bBuffer[256]; DWORD dwBytesRead = 0; while (WaitForSingleObject(pInfo->hEvent, 0) != WAIT_OBJECT_0) { bBuffer[0] = 0; dwBytesRead = ReadEventBytesFromPort(pInfo->pCommDevice, bBuffer, sizeof(bBuffer)); ASSERT(dwBytesRead <= sizeof(bBuffer)); if (dwBytesRead > 0) { if (bBuffer[0] == _T('\b')) SendMessage(pInfo->hWnd, pInfo->msgBackspace, 0, 0); else SendMessage(pInfo->hWnd, pInfo->msgOutput, 0, (LPARAM) bBuffer); } } delete pInfo; return 0; } ///////////////////////////////////////////////////////////////////////////// // CSerialDoc overrides BOOL CSerialDoc::SaveModified() { return TRUE; // don't allow saving } BOOL CSerialDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; ((CEditView*) m_viewList.GetHead())->SetWindowText(NULL); // // Create the COXSerialCommFile and COXSerialCommConfig objects // ASSERT(m_pCommConfig == NULL); if (m_pCommConfig == NULL && (m_pCommConfig = new COXSerialCommConfig()) == NULL) { TRACE(_T("Unable to allocate new COXSerialCommConfig object\n")); return -1; } ASSERT_VALID(m_pCommConfig); ASSERT(m_pCommFile == NULL); if (m_pCommFile == NULL && (m_pCommFile = new COXSerialCommFile()) == NULL) { TRACE(_T("Unable to allocate new COXSerialCommFile object\n")); return -1; } ASSERT_VALID(m_pCommFile); return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CSerialDoc message handlers void CSerialDoc::OnConnect() { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); if (m_pCommFile == NULL || m_pCommConfig == NULL) return; // // Attempt to make a connection // COXSerialCommException e; if (!m_pCommFile->Open(*m_pCommConfig, &e)) { TRACE(_T("Unable to open serial communication device\n")); TCHAR szBuffer[256]; e.GetErrorMessage(szBuffer, sizeof(szBuffer)); GetView()->Output(szBuffer); GetView()->Output(_T("\r\n")); return; } else { // // setup which events we want to recieve on this comm port // DWORD dwEventMask; if (m_pCommFile->GetCommMask(dwEventMask)) { dwEventMask |= EV_RXCHAR; VERIFY(m_pCommFile->SetCommMask(dwEventMask)); } else { GetView()->Output(_T("Error (#%d): Unable to set event states on %s.\r\n"), ::GetLastError(), m_pCommConfig->GetCommName()); } GetView()->Output(_T("A serial connection has been established on port %s.\r\n"), m_pCommConfig->GetCommName()); } // // Create the new thread (if not created before); If the user has already connected // before we resume the suspended thread. // if (m_pIncomingTextThread == NULL) { PTHREADINFO pInfo = new THREADINFO; if (pInfo == NULL) return; pInfo->hEvent = (HANDLE) m_eventClose; pInfo->pCommDevice = m_pCommFile; pInfo->msgOutput = IDM_OUTPUT; pInfo->msgBackspace = IDM_BACKSPACE; pInfo->hWnd = GetView()->GetSafeHwnd(); m_pIncomingTextThread = AfxBeginThread(IncomingTextThread, pInfo, THREAD_PRIORITY_IDLE); } else { // Thread already exists, resume it ASSERT_VALID(m_pIncomingTextThread); m_pIncomingTextThread->ResumeThread(); } } void CSerialDoc::OnUpdateConnect(CCmdUI* pCmdUI) { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); if (m_pCommFile == NULL || m_pCommConfig == NULL) pCmdUI->Enable(FALSE); else pCmdUI->Enable(m_pCommFile->IsOpen() == FALSE); } void CSerialDoc::OnDisconnect() { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); ASSERT_VALID(m_pIncomingTextThread); if (m_pCommFile == NULL || m_pCommConfig == NULL) return; // // Close the communications port and suspend the IncomingTextThread // (the thread is closed in the destructor) // m_pIncomingTextThread->SuspendThread(); m_pCommFile->Close(); GetView()->Output(_T("The communication port has been closed.\r\n")); } void CSerialDoc::OnUpdateDisconnect(CCmdUI* pCmdUI) { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); if (m_pCommFile == NULL || m_pCommConfig == NULL) pCmdUI->Enable(FALSE); else pCmdUI->Enable(m_pCommFile->IsOpen()); } void CSerialDoc::OnReceive() { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); ASSERT_VALID(m_pIncomingTextThread); if (m_pCommFile == NULL || m_pCommConfig == NULL) { GetView()->Output(_T("Invalid handles, unable to read.\r\n")); return; } ASSERT(m_pCommFile->IsOpen() == TRUE); // // Clear the send and receive queues m_pCommFile->PurgeRx(); m_pCommFile->PurgeTx(); GetView()->Output(_T("Waiting for incoming files...\r\n")); CTransferDlg dlg; // Setup the transfer dialog // dlg.m_bSending = FALSE; dlg.m_pCommFile = m_pCommFile; dlg.m_pFile = NULL; ///////////////// // Suspend the incoming Text thread m_pIncomingTextThread->SuspendThread(); // // Show the dialog // (the dialog will automatically terminate when the transfer is done) // if (dlg.DoModal() == IDCANCEL) { m_pCommFile->PurgeTx(); m_pCommFile->PurgeRx(); GetView()->Output(_T("Receive file cancelled by user.\r\n")); } else { GetView()->Output(dlg.m_sMessage); } ///////////////// // Resume the incoming Text thread m_pIncomingTextThread->ResumeThread(); } void CSerialDoc::OnUpdateReceive(CCmdUI* pCmdUI) { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); if (m_pCommFile == NULL || m_pCommConfig == NULL) pCmdUI->Enable(FALSE); else pCmdUI->Enable(m_pCommFile->IsOpen()); } void CSerialDoc::OnSend() { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); ASSERT_VALID(m_pIncomingTextThread); if (m_pCommFile == NULL || m_pCommConfig == NULL) { GetView()->Output(_T("Invalid handles, unable to write.\r\n")); return; } ASSERT(m_pCommFile->IsOpen() == TRUE); // // Purge the incoming and outgoing queues // m_pCommFile->PurgeRx(); m_pCommFile->PurgeTx(); // // Prompt the user for the file to send // CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _szAllFilesFilter); if (dlg.DoModal() == IDOK) { ///////////////// // Suspend the Incoming text thread m_pIncomingTextThread->SuspendThread(); CFile f; if (f.Open(dlg.GetPathName(), CFile::modeRead)) { GetView()->Output(_T("Sending file %s which is %d bytes long; "), dlg.GetPathName(), f.GetLength()); CTransferDlg tdlg; // // setup the transfer dialog // tdlg.m_sMessage.Format(_T("Waiting for other side to confirm transfer")); tdlg.m_sFilename = dlg.GetFileName(); tdlg.m_pCommFile = m_pCommFile; tdlg.m_pFile = &f; tdlg.m_bSending = TRUE; // // Show the dialog // if (tdlg.DoModal() == IDCANCEL) { m_pCommFile->PurgeTx(); m_pCommFile->PurgeRx(); GetView()->Output(_T("Transfer cancelled.\r\n")); } else { GetView()->Output(tdlg.m_sMessage); } } else GetView()->Output(_T("Error: Unable to send; There was an error opening the file.\r\n")); ///////////////// // Resume the Incoming Text Thread m_pIncomingTextThread->ResumeThread(); } } void CSerialDoc::OnUpdateSend(CCmdUI* pCmdUI) { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); if (m_pCommFile == NULL || m_pCommConfig == NULL) pCmdUI->Enable(FALSE); else pCmdUI->Enable(m_pCommFile->IsOpen()); } void CSerialDoc::OnSerialSetup() { ASSERT_VALID(m_pCommConfig); if (m_pCommConfig != NULL) { // // Allow the user to edit COMM properties // // COXSerialCommConfig takes care of data validation m_pCommConfig->DoConfigDialog(); } } void CSerialDoc::OnUpdateSerialSetup(CCmdUI* pCmdUI) { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); if (m_pCommFile == NULL || m_pCommConfig == NULL) pCmdUI->Enable(FALSE); else pCmdUI->Enable(!m_pCommFile->IsOpen()); } void CSerialDoc::OnSetTimeout() { ASSERT_VALID(m_pCommConfig); if (m_pCommConfig != NULL) { // // Allow user to change Send/Receive timeouts by showing the CTimeoutDlg // dialog. // CTimeoutDlg dlg; dlg.m_dwRxTimeout = m_dwRxTimeout; dlg.m_dwTxTimeout = m_dwTxTimeout; if (dlg.DoModal() == IDOK) { m_dwRxTimeout = dlg.m_dwRxTimeout; m_dwTxTimeout = dlg.m_dwTxTimeout; GetView()->Output(_T("Timeout values set to Rx: %d, Tx: %d.\r\n"), m_dwRxTimeout, m_dwTxTimeout); } } } void CSerialDoc::OnUpdateSetTimeout(CCmdUI* pCmdUI) { ASSERT_VALID(m_pCommFile); ASSERT_VALID(m_pCommConfig); if (m_pCommFile == NULL || m_pCommConfig == NULL) pCmdUI->Enable(FALSE); else pCmdUI->Enable(!m_pCommFile->IsOpen()); }