📄 win32port.cpp
字号:
// different types of handshaking, plus the settings for the desired
// output of DTR and RTS. Once all of these things are punched into
// the DCB, a call to SetCommState() will take care of updating
// the physical device.
//
// There are a lot of things that can go wrong when attempting to set
// the port. We don't consider any of these things to be fatal errors,
// we simply pass the error back to the calling routine. However, the
// calling routine might decide to treat it as a fatal error. The
// constructor does this, treating an error at this point as fatal.
//
// Note also that this routine relies heavily on the DCB support class.
// It takes care of writing the correct values to its members by means
// of a bunch of accessor functions.
//
RS232Error Win32Port::write_settings()
{
RS232Error error = RS232_SUCCESS;
m_Dcb.SetBaudRate( settings.BaudRate );
m_Dcb.SetParity( settings.Parity, error );
m_Dcb.SetWordLength( settings.WordLength, error );
m_Dcb.SetStopBits( settings.StopBits, error );
//
// Even though we think that we're setting up DTR and RTS,
// we might not actually be pulling it off. If one of the
// two hardware handshaking protocols is enabled, it will
// wipe out the DCB setting for the corresponding control
// line and change it to use handshaking instead.
//
m_Dcb.SetDtr( settings.Dtr );
m_Dcb.SetRts( settings.Rts );
m_Dcb.SetXonXoff( settings.XonXoff );
m_Dcb.SetRtsCts( settings.RtsCts );
m_Dcb.SetDtrDsr( settings.DtrDsr );
SetCommState( m_hPort, &m_Dcb );
if ( GetLastError() != 0 ) {
if ( GetLastError() == ERROR_INVALID_HANDLE )
return (RS232Error) WIN32_SETTINGS_FAILURE;
else {
m_dwWindowsError = GetLastError();
return (RS232Error) WIN32_CHECK_WINDOWS_ERROR;
}
}
return error;
}
//
// read_settings() is an internal support routine only used by member
// functions of Win32Port. It is actually only used in one place: the
// constructor. It is responsible for reading the settings from the
// port as soon as we open it, giving us a baseline to start with.
// If necessary, this means that we can open the port in the default
// state that the system wants it to be opened in.
//
// Note that I didn't add accessor functions to the DCB to pull these
// settings out, we just look directly at the DCB members to get them.
// It would have probably been better to add translation routines, but
// it is not really an important issue.
//
void Win32Port::read_settings()
{
DCB dcb;
GetCommState( m_hPort, &dcb );
settings.BaudRate = dcb.BaudRate;
if ( !dcb.fParity )
settings.Parity = 'N';
else
switch ( dcb.Parity ) {
case EVENPARITY : settings.Parity = 'E'; break;
case ODDPARITY : settings.Parity = 'O'; break;
case MARKPARITY : settings.Parity = 'M'; break;
case SPACEPARITY : settings.Parity = 'S'; break;
default : settings.Parity = 'N'; break;
}
settings.WordLength = dcb.ByteSize;
if ( dcb.StopBits == ONESTOPBIT )
settings.StopBits = 1;
else
settings.StopBits = 2;
if ( dcb.fDtrControl == DTR_CONTROL_DISABLE )
settings.Dtr = 0;
else
settings.Dtr = 1;
if ( dcb.fRtsControl == RTS_CONTROL_DISABLE )
settings.Rts = 0;
else
settings.Rts = 1;
if ( dcb.fOutX || dcb.fInX )
settings.XonXoff = 1;
else
settings.XonXoff = 0;
if ( dcb.fOutxCtsFlow || dcb.fRtsControl == RTS_CONTROL_HANDSHAKE )
settings.RtsCts = 1;
else
settings.RtsCts = 0;
if ( dcb.fOutxDsrFlow || dcb.fDtrControl == DTR_CONTROL_HANDSHAKE )
settings.DtrDsr = 1;
else
settings.DtrDsr = 0;
}
//
// translate_last_error() is called any time an error occurs when
// a Win32 API function is called. We know how to translate a couple
// of Win32 errors to native versions, but for the most part we just
// pass the responsibility for interpretation on to the caller of the
// Win32Port function. The value we see is stored in the m_dwWindowsError
// member so that it can be checked even in the presence of other
// intervening problems.
//
RS232Error Win32Port::translate_last_error()
{
switch ( m_dwWindowsError = GetLastError() )
{
case ERROR_ACCESS_DENIED : return error_status = RS232_PORT_IN_USE;
case ERROR_FILE_NOT_FOUND : return error_status = RS232_PORT_NOT_FOUND;
}
return error_status = (RS232Error) WIN32_CHECK_WINDOWS_ERROR;
}
//
// InputThread is the function that runs as a separate thread that is
// responsible for reading all input data and status information. It is
// a fairly complex thread, which makes it a bit harder to read than
// it should be. Outside of setup and teardown, the routine sits in
// a giant loop that basically performs two functions. First, it makes
// sure that it is properly set up to be notified when either incoming
// data or status messages arrive. Status messages consist of line status
// errors and modem status changes. (Note that we don't try to read if
// there is no room for data in the input buffer.) Once that is set up,
// we simply wait for one of four potential events to be signaled. The
// events are 1) a kill message fromt the main thread, which comes when
// the port is being closed, 2) incoming data that has been read in from
// the serial port, 3) a line status error or modem status change on the
// serial port, and 4) a read request message, which indicates that some
// room may have been opened up in the input buffer.
//
// The rest of the work in the routine is devoted to figuring out what
// to do in response to those incoming events.
//
void Win32Port::InputThread( void * arglist )
{
//
// The thread is passed a pointer to the Win32Port object when it is
// started up. We have to cast that back to a Win32Port pointer,
// because of the way Win32 starts a thread function. Since this is
// a static member function of the class, that gives us carte blanche
// to access all the protected members of the class. It also means we
// don't have to mess with creation or management of thread-specific
// data, as anything we need will be in the object itself.
//
Win32Port *port = (Win32Port *) arglist;
//
// We call these two functions once when we start up so that all of
// the initial settings in the modem status and line status words
// are initialized properly. This also guarantees that the notification
// functions for the modem status will be called once with the initial
// values, which will often be a useful thing for the calling program.
//
port->check_modem_status( true, 0 );
port->clear_error();
//
// We need OVERLAPPED structures for both of our overlapped
// functions in this thread: the data read called with ReadFile(),
// and the status read called with WaitCommEvent. We could have included
// these in the Win32Port object, but since they aren't used
// anywhere else, it seemed better to confine them to being automatic
// objects. Each of these objects gets an event that we create here
// as well, they are the events used to signal us when we are
// waiting in the big loop.
//
OVERLAPPED AsyncReadInfo = { 0 };
OVERLAPPED AsyncStatusInfo = { 0 };
AsyncReadInfo.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
AsyncStatusInfo.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
assert( AsyncReadInfo.hEvent );
assert( AsyncStatusInfo.hEvent );
//
// This word is used as an argument to WaitForCommEvent()
//
DWORD dwCommEvent;
//
// Some initialization
//
bool waiting_on_status = false;
port->m_bInputThreadReading = false;
//
// This array holds the four event handles that are used to
// signal this thread. On each pass through the main loop we
// will be waiting for one of the four to go to the signal
// state, at which time we take action on it.
//
HANDLE handles[ 4 ] = { port->m_hKillInputThreadEvent,
AsyncReadInfo.hEvent,
AsyncStatusInfo.hEvent,
port->m_hReadRequestEvent };
//
// We set all of the conditions that we will be waiting for.
// Note the inclusion of EV_RINGTE, which is an undocumented
// flag that we define in Win32Port.h.
//
SetCommMask( port->m_hPort,
EV_BREAK | EV_CTS | EV_DSR | EV_RXCHAR |
EV_ERR | EV_RING | EV_RLSD | EV_RINGTE );
//
// This is the main loop. It executes until the done flag is set,
// which won't happen until the kill thread message is sent.
// The first part of the loop sets up the read actions, the second
// part waits for something to happen, and the final part of the
// loop deals with whatever happened.
//
for ( bool done = false ; !done ; ) {
//
// Under normal conditions this loop should have a read action
// in progress at all times. The only time this won't be true
// is when there is no room in the RX queue. We have a member
// in class Win32Port that defines whether or not a read is
// presently active. This section of code just makes sure that
// if no read is currently in progress, we do our best to get
// one started.
//
int bytes_to_read = 0;
char read_buffer[ 256 ];
DWORD dwBytesRead;
if ( !port->m_bInputThreadReading ) {
bytes_to_read = port->m_RxQueue.SpaceFree();
if ( bytes_to_read > 256 )
bytes_to_read = 256;
//
// If there is room to add new bytes to the RX queue, and
// we currently aren't reading anything, we kick off the
// read right here with a call to ReadFile(). There are two
// possible things that can then happen. If there isn't any
// data in the buffer, ReadFile() can return immediately
// with the actual input in progress but not complete. If
// there was enough data in the input stream already to
// fulfill the read, it might return with data present.
//
if ( bytes_to_read > 0 ) {
if ( !ReadFile( port->m_hPort, read_buffer, bytes_to_read, &dwBytesRead, &AsyncReadInfo ) )
{
// The only acceptable error condition is the I/O
// pending error, which isn't really an error, it
// just means the read has been deferred and will
// be performed using overlapped I/O.
port->m_bInputThreadReading = true;
} else {
// If we reach this point, ReadFile() returned
// immediately, presumably because it was able
// to fill the I/O request. I put all of the bytes
// just read into the RX queue, then call the
// notification routine that should alert the caller
// to the fact that some data has arrive.
if ( dwBytesRead ) {
port->m_RxQueue.Insert( read_buffer, dwBytesRead );
port->RxNotify( dwBytesRead );
}
}
} else {
// If we reach this point, it means there is no room in
// the RX queue. We reset the read event (just in case)
// and go on to the rest of the code in this loop.
ResetEvent( AsyncReadInfo.hEvent );
}
}
//
// Unlike the read event, we will unconditionally always have
// a status even read in progress. The flag waiting_on_status
// shows us whether or not we are currently waiting. If not,
// we have to make a call to WaitCommEvent() so that one gets
// kicked off.
//
if ( !waiting_on_status ) {
if ( !WaitCommEvent( port->m_hPort,
&dwCommEvent,
&AsyncStatusInfo ) ) {
// WaitCommEvent() can return immediately if there are
// status events queued up and waiting for us to read.
// But normally it should return with an error code of
// ERROR_IO_PENDING, which means that no events are
// currently queued, and we will have to wait for
// something noteworthy to happen.
waiting_on_status = true;
} else {
// If we reach this point it means that WaitCommEvent()
// returned immediately, so either a line status error
// or a modem line state change has occurred. These two
// routines are called to deal with all those possibilities.
// The event bits are in dwCommEvent, which was passed to
// WaitCommEvent() when we called it. The first of these
// two functions handles all changes in modem status lines,
// the second deals with line status errors.
port->check_modem_status( false, dwCommEvent );
port->clear_error();
}
}
//
// We've completed the preliminary part of the loop and we are
// now read to wait for something to happen. Note that it is
// possible that either the call to ReadFile() or
// WaitCommEvent() returned immediately, in which case we aren't
// actively waiting for data of that event type. If that's true,
// we have to go back through the loop and try to set up the
// ReadFile() or WaitCommEvent() again. That's what this first
// conditional statement is checking for. It would be a simpler
// statement, but we have to take into account the possibility
// that we aren't reading because there is no room in the
// RX queue, in which case we can wait right away.
if ( waiting_on_status &&
( port->m_bInputThreadReading || bytes_to_read == 0 ) ) {
DWORD result = WaitForMultipleObjects( 4,
handles,
FALSE,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -