serialport.cpp
上传用户:hxj5298
上传日期:2007-01-03
资源大小:18k
文件大小:19k
- /*
- Module : SERIALPORT.CPP
- Purpose: Implementation for an MFC wrapper class for serial ports
- Created: PJN / 31-05-1999
- History: PJN / 03-06-1999 1. Fixed problem with code using CancelIo which does not exist on 95.
- 2. Fixed leaks which can occur in sample app when an exception is thrown
- PJN / 16-06-1999 1. Fixed a bug whereby CString::ReleaseBuffer was not being called in
- CSerialException::GetErrorMessage
- PJN / 29-09-1999 1. Fixed a simple copy and paste bug in CSerialPort::SetDTR
- Copyright (c) 1999 by PJ Naughter.
- All rights reserved.
- */
- ///////////////////////////////// Includes //////////////////////////////////
- #include "stdafx.h"
- #include "serialport.h"
- #include "winerror.h"
- ///////////////////////////////// defines /////////////////////////////////////
- #ifdef _DEBUG
- #define new DEBUG_NEW
- #undef THIS_FILE
- static char THIS_FILE[] = __FILE__;
- #endif
- //////////////////////////////// Implementation ///////////////////////////////
- //Class which handles CancelIo function which must be constructed at run time
- //since it is not imeplemented on NT 3.51 or Windows 95. To avoid the loader
- //bringing up a message such as "Failed to load due to missing export...", the
- //function is constructed using GetProcAddress. The CSerialPort::CancelIo
- //function then checks to see if the function pointer is NULL and if it is it
- //throws an exception using the error code ERROR_CALL_NOT_IMPLEMENTED which
- //is what 95 would have done if it had implemented a stub for it in the first
- //place !!
- class _SERIAL_PORT_DATA
- {
- public:
- //Constructors /Destructors
- _SERIAL_PORT_DATA();
- ~_SERIAL_PORT_DATA();
- HINSTANCE m_hKernel32;
- typedef BOOL (CANCELIO)(HANDLE);
- typedef CANCELIO* LPCANCELIO;
- LPCANCELIO m_lpfnCancelIo;
- };
- _SERIAL_PORT_DATA::_SERIAL_PORT_DATA()
- {
- m_hKernel32 = LoadLibrary(_T("KERNEL32.DLL"));
- VERIFY(m_hKernel32 != NULL);
- m_lpfnCancelIo = (LPCANCELIO) GetProcAddress(m_hKernel32, "CancelIo");
- }
- _SERIAL_PORT_DATA::~_SERIAL_PORT_DATA()
- {
- FreeLibrary(m_hKernel32);
- m_hKernel32 = NULL;
- }
- //The local variable which handle the function pointers
- _SERIAL_PORT_DATA _SerialPortData;
- ////////// Exception handling code
- void AfxThrowSerialException(DWORD dwError /* = 0 */)
- {
- if (dwError == 0)
- dwError = ::GetLastError();
- CSerialException* pException = new CSerialException(dwError);
- TRACE(_T("Warning: throwing CSerialException for error %dn"), dwError);
- THROW(pException);
- }
- BOOL CSerialException::GetErrorMessage(LPTSTR pstrError, UINT nMaxError, PUINT pnHelpContext)
- {
- ASSERT(pstrError != NULL && AfxIsValidString(pstrError, nMaxError));
- if (pnHelpContext != NULL)
- *pnHelpContext = 0;
- LPTSTR lpBuffer;
- BOOL bRet = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
- NULL, m_dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT),
- (LPTSTR) &lpBuffer, 0, NULL);
- if (bRet == FALSE)
- *pstrError = ' ';
- else
- {
- lstrcpyn(pstrError, lpBuffer, nMaxError);
- bRet = TRUE;
- LocalFree(lpBuffer);
- }
- return bRet;
- }
- CString CSerialException::GetErrorMessage()
- {
- CString rVal;
- LPTSTR pstrError = rVal.GetBuffer(4096);
- GetErrorMessage(pstrError, 4096, NULL);
- rVal.ReleaseBuffer();
- return rVal;
- }
- CSerialException::CSerialException(DWORD dwError)
- {
- m_dwError = dwError;
- }
- CSerialException::~CSerialException()
- {
- }
- IMPLEMENT_DYNAMIC(CSerialException, CException)
- #ifdef _DEBUG
- void CSerialException::Dump(CDumpContext& dc) const
- {
- CObject::Dump(dc);
- dc << "m_dwError = " << m_dwError;
- }
- #endif
- ////////// The actual serial port code
- CSerialPort::CSerialPort()
- {
- m_hComm = INVALID_HANDLE_VALUE;
- m_bOverlapped = FALSE;
- }
- CSerialPort::~CSerialPort()
- {
- Close();
- }
- IMPLEMENT_DYNAMIC(CSerialPort, CObject)
- #ifdef _DEBUG
- void CSerialPort::Dump(CDumpContext& dc) const
- {
- CObject::Dump(dc);
- dc << _T("m_hComm = ") << m_hComm << _T("n");
- dc << _T("m_bOverlapped = ") << m_bOverlapped;
- }
- #endif
- void CSerialPort::Open(int nPort, DWORD dwBaud, Parity parity, BYTE DataBits, StopBits stopbits, FlowControl fc, BOOL bOverlapped)
- {
- //Validate our parameters
- ASSERT(nPort>0 && nPort<=255);
- //Call CreateFile to open up the comms port
- CString sPort;
- sPort.Format(_T("\\.\COM%d"), nPort);
- m_hComm = CreateFile(sPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, bOverlapped ? FILE_FLAG_OVERLAPPED : 0, NULL);
- if (m_hComm == INVALID_HANDLE_VALUE)
- {
- TRACE(_T("Failed to open up the comms portn"));
- AfxThrowSerialException();
- }
-
- m_bOverlapped = bOverlapped;
- //Get the current state prior to changing it
- DCB dcb;
- GetState(dcb);
- //Setup the baud rate
- dcb.BaudRate = dwBaud;
- //Setup the Parity
- switch (parity)
- {
- case EvenParity: dcb.Parity = EVENPARITY; break;
- case MarkParity: dcb.Parity = MARKPARITY; break;
- case NoParity: dcb.Parity = NOPARITY; break;
- case OddParity: dcb.Parity = ODDPARITY; break;
- case SpaceParity: dcb.Parity = SPACEPARITY; break;
- default: ASSERT(FALSE); break;
- }
- //Setup the data bits
- dcb.ByteSize = DataBits;
- //Setup the stop bits
- switch (stopbits)
- {
- case OneStopBit: dcb.StopBits = ONESTOPBIT; break;
- case OnePointFiveStopBits: dcb.StopBits = ONE5STOPBITS; break;
- case TwoStopBits: dcb.StopBits = TWOSTOPBITS; break;
- default: ASSERT(FALSE); break;
- }
- //Setup the flow control
- dcb.fDsrSensitivity = FALSE;
- switch (fc)
- {
- case NoFlowControl:
- {
- dcb.fOutxCtsFlow = FALSE;
- dcb.fOutxDsrFlow = FALSE;
- dcb.fOutX = FALSE;
- dcb.fInX = FALSE;
- break;
- }
- case CtsRtsFlowControl:
- {
- dcb.fOutxCtsFlow = TRUE;
- dcb.fOutxDsrFlow = FALSE;
- dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
- dcb.fOutX = FALSE;
- dcb.fInX = FALSE;
- break;
- }
- case CtsDtrFlowControl:
- {
- dcb.fOutxCtsFlow = TRUE;
- dcb.fOutxDsrFlow = FALSE;
- dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
- dcb.fOutX = FALSE;
- dcb.fInX = FALSE;
- break;
- }
- case DsrRtsFlowControl:
- {
- dcb.fOutxCtsFlow = FALSE;
- dcb.fOutxDsrFlow = TRUE;
- dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
- dcb.fOutX = FALSE;
- dcb.fInX = FALSE;
- break;
- }
- case DsrDtrFlowControl:
- {
- dcb.fOutxCtsFlow = FALSE;
- dcb.fOutxDsrFlow = TRUE;
- dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
- dcb.fOutX = FALSE;
- dcb.fInX = FALSE;
- break;
- }
- case XonXoffFlowControl:
- {
- dcb.fOutxCtsFlow = FALSE;
- dcb.fOutxDsrFlow = FALSE;
- dcb.fOutX = TRUE;
- dcb.fInX = TRUE;
- dcb.XonChar = 0x11;
- dcb.XoffChar = 0x13;
- dcb.XoffLim = 100;
- dcb.XonLim = 100;
- break;
- }
- default:
- {
- ASSERT(FALSE);
- break;
- }
- }
-
- //Now that we have all the settings in place, make the changes
- SetState(dcb);
- }
- void CSerialPort::Close()
- {
- if (IsOpen())
- {
- BOOL bSuccess = CloseHandle(m_hComm);
- m_hComm = INVALID_HANDLE_VALUE;
- if (!bSuccess)
- TRACE(_T("Failed to close up the comms port, GetLastError:%dn"), GetLastError());
- m_bOverlapped = FALSE;
- }
- }
- void CSerialPort::Attach(HANDLE hComm)
- {
- Close();
- m_hComm = hComm;
- }
- HANDLE CSerialPort::Detach()
- {
- HANDLE hrVal = m_hComm;
- m_hComm = INVALID_HANDLE_VALUE;
- return hrVal;
- }
- DWORD CSerialPort::Read(void* lpBuf, DWORD dwCount)
- {
- ASSERT(IsOpen());
- ASSERT(!m_bOverlapped);
- DWORD dwBytesRead = 0;
- if (!ReadFile(m_hComm, lpBuf, dwCount, &dwBytesRead, NULL))
- {
- TRACE(_T("Failed in call to ReadFilen"));
- AfxThrowSerialException();
- }
- return dwBytesRead;
- }
- BOOL CSerialPort::Read(void* lpBuf, DWORD dwCount, OVERLAPPED& overlapped)
- {
- ASSERT(IsOpen());
- ASSERT(m_bOverlapped);
- ASSERT(overlapped.hEvent);
- DWORD dwBytesRead = 0;
- BOOL bSuccess = ReadFile(m_hComm, lpBuf, dwCount, &dwBytesRead, &overlapped);
- if (!bSuccess)
- {
- if (GetLastError() != ERROR_IO_PENDING)
- {
- TRACE(_T("Failed in call to ReadFilen"));
- AfxThrowSerialException();
- }
- }
- return bSuccess;
- }
- DWORD CSerialPort::Write(const void* lpBuf, DWORD dwCount)
- {
- ASSERT(IsOpen());
- ASSERT(!m_bOverlapped);
- DWORD dwBytesWritten = 0;
- if (!WriteFile(m_hComm, lpBuf, dwCount, &dwBytesWritten, NULL))
- {
- TRACE(_T("Failed in call to WriteFilen"));
- AfxThrowSerialException();
- }
- return dwBytesWritten;
- }
- BOOL CSerialPort::Write(const void* lpBuf, DWORD dwCount, OVERLAPPED& overlapped)
- {
- ASSERT(IsOpen());
- ASSERT(m_bOverlapped);
- ASSERT(overlapped.hEvent);
- DWORD dwBytesWritten = 0;
- BOOL bSuccess = WriteFile(m_hComm, lpBuf, dwCount, &dwBytesWritten, &overlapped);
- if (!bSuccess)
- {
- if (GetLastError() != ERROR_IO_PENDING)
- {
- TRACE(_T("Failed in call to WriteFilen"));
- AfxThrowSerialException();
- }
- }
- return bSuccess;
- }
- void CSerialPort::GetOverlappedResult(OVERLAPPED& overlapped, DWORD& dwBytesTransferred, BOOL bWait)
- {
- ASSERT(IsOpen());
- ASSERT(m_bOverlapped);
- ASSERT(overlapped.hEvent);
- DWORD dwBytesWritten = 0;
- if (!::GetOverlappedResult(m_hComm, &overlapped, &dwBytesTransferred, bWait))
- {
- if (GetLastError() != ERROR_IO_PENDING)
- {
- TRACE(_T("Failed in call to GetOverlappedResultn"));
- AfxThrowSerialException();
- }
- }
- }
- void CSerialPort::_OnCompletion(DWORD dwErrorCode, DWORD dwCount, LPOVERLAPPED lpOverlapped)
- {
- //Validate our parameters
- ASSERT(lpOverlapped);
- //Convert back to the C++ world
- CSerialPort* pSerialPort = (CSerialPort*) lpOverlapped->hEvent;
- ASSERT(pSerialPort->IsKindOf(RUNTIME_CLASS(CSerialPort)));
- //Call the C++ function
- pSerialPort->OnCompletion(dwErrorCode, dwCount, lpOverlapped);
- }
- void CSerialPort::OnCompletion(DWORD /*dwErrorCode*/, DWORD /*dwCount*/, LPOVERLAPPED lpOverlapped)
- {
- //Just free up the memory which was previously allocated for the OVERLAPPED structure
- delete lpOverlapped;
- //Your derived classes can do something useful in OnCompletion, but don't forget to
- //call CSerialPort::OnCompletion to ensure the memory is freed up
- }
- void CSerialPort::CancelIo()
- {
- ASSERT(IsOpen());
- if (_SerialPortData.m_lpfnCancelIo == NULL)
- {
- TRACE(_T("CancelIo function is not supported on this OS. You need to be running at least NT 4 or Win 98 to use this functionn"));
- AfxThrowSerialException(ERROR_CALL_NOT_IMPLEMENTED);
- }
- if (!::_SerialPortData.m_lpfnCancelIo(m_hComm))
- {
- TRACE(_T("Failed in call to CancelIOn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::WriteEx(const void* lpBuf, DWORD dwCount)
- {
- ASSERT(IsOpen());
- OVERLAPPED* pOverlapped = new OVERLAPPED;
- ZeroMemory(pOverlapped, sizeof(OVERLAPPED));
- pOverlapped->hEvent = (HANDLE) this;
- if (!WriteFileEx(m_hComm, lpBuf, dwCount, pOverlapped, _OnCompletion))
- {
- delete pOverlapped;
- TRACE(_T("Failed in call to WriteFileExn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::ReadEx(void* lpBuf, DWORD dwCount)
- {
- ASSERT(IsOpen());
- OVERLAPPED* pOverlapped = new OVERLAPPED;
- ZeroMemory(pOverlapped, sizeof(OVERLAPPED));
- pOverlapped->hEvent = (HANDLE) this;
- if (!ReadFileEx(m_hComm, lpBuf, dwCount, pOverlapped, _OnCompletion))
- {
- delete pOverlapped;
- TRACE(_T("Failed in call to ReadFileExn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::TransmitChar(char cChar)
- {
- ASSERT(IsOpen());
- if (!TransmitCommChar(m_hComm, cChar))
- {
- TRACE(_T("Failed in call to TransmitCommCharn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::GetConfig(COMMCONFIG& config)
- {
- ASSERT(IsOpen());
- DWORD dwSize = sizeof(COMMCONFIG);
- if (!GetCommConfig(m_hComm, &config, &dwSize))
- {
- TRACE(_T("Failed in call to GetCommConfign"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::SetConfig(COMMCONFIG& config)
- {
- ASSERT(IsOpen());
- DWORD dwSize = sizeof(COMMCONFIG);
- if (!SetCommConfig(m_hComm, &config, dwSize))
- {
- TRACE(_T("Failed in call to SetCommConfign"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::SetBreak()
- {
- ASSERT(IsOpen());
- if (!SetCommBreak(m_hComm))
- {
- TRACE(_T("Failed in call to SetCommBreakn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::ClearBreak()
- {
- ASSERT(IsOpen());
- if (!ClearCommBreak(m_hComm))
- {
- TRACE(_T("Failed in call to SetCommBreakn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::ClearError(DWORD& dwErrors)
- {
- ASSERT(IsOpen());
- if (!ClearCommError(m_hComm, &dwErrors, NULL))
- {
- TRACE(_T("Failed in call to ClearCommErrorn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::GetDefaultConfig(int nPort, COMMCONFIG& config)
- {
- //Validate our parameters
- ASSERT(nPort>0 && nPort<=255);
- //Create the device name as a string
- CString sPort;
- sPort.Format(_T("COM%d"), nPort);
- DWORD dwSize = sizeof(COMMCONFIG);
- if (!GetDefaultCommConfig(sPort, &config, &dwSize))
- {
- TRACE(_T("Failed in call to GetDefaultCommConfign"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::SetDefaultConfig(int nPort, COMMCONFIG& config)
- {
- //Validate our parameters
- ASSERT(nPort>0 && nPort<=255);
- //Create the device name as a string
- CString sPort;
- sPort.Format(_T("COM%d"), nPort);
- DWORD dwSize = sizeof(COMMCONFIG);
- if (!SetDefaultCommConfig(sPort, &config, dwSize))
- {
- TRACE(_T("Failed in call to GetDefaultCommConfign"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::GetStatus(COMSTAT& stat)
- {
- ASSERT(IsOpen());
- DWORD dwErrors;
- if (!ClearCommError(m_hComm, &dwErrors, &stat))
- {
- TRACE(_T("Failed in call to ClearCommErrorn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::GetState(DCB& dcb)
- {
- ASSERT(IsOpen());
- if (!GetCommState(m_hComm, &dcb))
- {
- TRACE(_T("Failed in call to GetCommStaten"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::SetState(DCB& dcb)
- {
- ASSERT(IsOpen());
- if (!SetCommState(m_hComm, &dcb))
- {
- TRACE(_T("Failed in call to SetCommStaten"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::Escape(DWORD dwFunc)
- {
- ASSERT(IsOpen());
- if (!EscapeCommFunction(m_hComm, dwFunc))
- {
- TRACE(_T("Failed in call to EscapeCommFunctionn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::ClearDTR()
- {
- Escape(CLRDTR);
- }
- void CSerialPort::ClearRTS()
- {
- Escape(CLRRTS);
- }
- void CSerialPort::SetDTR()
- {
- Escape(SETDTR);
- }
- void CSerialPort::SetRTS()
- {
- Escape(SETRTS);
- }
- void CSerialPort::SetXOFF()
- {
- Escape(SETXOFF);
- }
- void CSerialPort::SetXON()
- {
- Escape(SETXON);
- }
- void CSerialPort::GetProperties(COMMPROP& properties)
- {
- ASSERT(IsOpen());
- if (!GetCommProperties(m_hComm, &properties))
- {
- TRACE(_T("Failed in call to GetCommPropertiesn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::GetModemStatus(DWORD& dwModemStatus)
- {
- ASSERT(IsOpen());
- if (!GetCommModemStatus(m_hComm, &dwModemStatus))
- {
- TRACE(_T("Failed in call to GetCommModemStatusn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::SetMask(DWORD dwMask)
- {
- ASSERT(IsOpen());
- if (!SetCommMask(m_hComm, dwMask))
- {
- TRACE(_T("Failed in call to SetCommMaskn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::GetMask(DWORD& dwMask)
- {
- ASSERT(IsOpen());
- if (!GetCommMask(m_hComm, &dwMask))
- {
- TRACE(_T("Failed in call to GetCommMaskn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::Flush()
- {
- ASSERT(IsOpen());
- if (!FlushFileBuffers(m_hComm))
- {
- TRACE(_T("Failed in call to FlushFileBuffersn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::Purge(DWORD dwFlags)
- {
- ASSERT(IsOpen());
- if (!PurgeComm(m_hComm, dwFlags))
- {
- TRACE(_T("Failed in call to PurgeCommn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::TerminateOutstandingWrites()
- {
- Purge(PURGE_TXABORT);
- }
- void CSerialPort::TerminateOutstandingReads()
- {
- Purge(PURGE_RXABORT);
- }
- void CSerialPort::ClearWriteBuffer()
- {
- Purge(PURGE_TXCLEAR);
- }
- void CSerialPort::ClearReadBuffer()
- {
- Purge(PURGE_RXCLEAR);
- }
- void CSerialPort::Setup(DWORD dwInQueue, DWORD dwOutQueue)
- {
- ASSERT(IsOpen());
- if (!SetupComm(m_hComm, dwInQueue, dwOutQueue))
- {
- TRACE(_T("Failed in call to SetupCommn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::SetTimeouts(COMMTIMEOUTS& timeouts)
- {
- ASSERT(IsOpen());
- if (!SetCommTimeouts(m_hComm, &timeouts))
- {
- TRACE(_T("Failed in call to SetCommTimeoutsn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::GetTimeouts(COMMTIMEOUTS& timeouts)
- {
- ASSERT(IsOpen());
- if (!GetCommTimeouts(m_hComm, &timeouts))
- {
- TRACE(_T("Failed in call to GetCommTimeoutsn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::Set0Timeout()
- {
- COMMTIMEOUTS Timeouts;
- ZeroMemory(&Timeouts, sizeof(COMMTIMEOUTS));
- Timeouts.ReadIntervalTimeout = MAXDWORD;
- Timeouts.ReadTotalTimeoutMultiplier = 0;
- Timeouts.ReadTotalTimeoutConstant = 0;
- Timeouts.WriteTotalTimeoutMultiplier = 0;
- Timeouts.WriteTotalTimeoutConstant = 0;
- SetTimeouts(Timeouts);
- }
- void CSerialPort::Set0WriteTimeout()
- {
- COMMTIMEOUTS Timeouts;
- GetTimeouts(Timeouts);
- Timeouts.WriteTotalTimeoutMultiplier = 0;
- Timeouts.WriteTotalTimeoutConstant = 0;
- SetTimeouts(Timeouts);
- }
- void CSerialPort::Set0ReadTimeout()
- {
- COMMTIMEOUTS Timeouts;
- GetTimeouts(Timeouts);
- Timeouts.ReadIntervalTimeout = MAXDWORD;
- Timeouts.ReadTotalTimeoutMultiplier = 0;
- Timeouts.ReadTotalTimeoutConstant = 0;
- SetTimeouts(Timeouts);
- }
- void CSerialPort::WaitEvent(DWORD& dwMask)
- {
- ASSERT(IsOpen());
- ASSERT(!m_bOverlapped);
- if (!WaitCommEvent(m_hComm, &dwMask, NULL))
- {
- TRACE(_T("Failed in call to WaitCommEventn"));
- AfxThrowSerialException();
- }
- }
- void CSerialPort::WaitEvent(DWORD& dwMask, OVERLAPPED& overlapped)
- {
- ASSERT(IsOpen());
- ASSERT(m_bOverlapped);
- ASSERT(overlapped.hEvent);
- if (!WaitCommEvent(m_hComm, &dwMask, &overlapped))
- {
- if (GetLastError() != ERROR_IO_PENDING)
- {
- TRACE(_T("Failed in call to WaitCommEventn"));
- AfxThrowSerialException();
- }
- }
- }