📄 win32port.cpp
字号:
//
// WIN32PORT.CPP
//
// Source code from:
//
// Serial Communications: A C++ Developer's Guide, 2nd Edition
// by Mark Nelson, M&T Books, 1999
//
// Please see the book for information on usage.
//
// This file contains the class implementation for Win32Port.
// Win32Port implements a version of class RS232 that works with
// the Win32 serial API. The implementation of this class is in
// file Win32Port. Although the sample programs in this book use
// MFC, this class should work independently of any application
// framework.
//
//
#include <process.h>
#include <cassert>
#include <sstream>
#include <iomanip>
using namespace std;
#include "Win32Port.h"
//
// Under MFC, some heap use tracking is done in debug mode, in an
// attempt to track down memory leaks. This code enables that.
// Feel free to delete the five lines that enable this feature,
// as they are not necessary for proper operation of the
// Win32Port class.
//
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//
// The arguments to the Win32Port constructor are the same as the
// arguments passed to the constructurs for all of the other
// RS232 derived classes, with one noticeable difference. Instead
// of passing a numeric value such as COM1 or COM2, this guy
// expects a string, e.g. "COM2". This is necessary to accomodate
// two special cases. First, com ports greater than 9 must be
// opened with a special string that looks like this:
// "\\.\COM10". Second, some drivers could be using a completely
// different name, like "USB Port 1".
//
//
// The constructor has a member initialization list that creates
// the input and output queues at the size chosen by the
// enumerated value in the class definition. The MTQueue class
// limits insertions into the internal deque<char> member to the
// value specified in the constructor.
//
Win32Port::Win32Port( const string &port,
long baud_rate /* = UNCHANGED */,
char parity /* = UNCHANGED */,
int word_length /* = UNCHANGED */,
int stop_bits /* = UNCHANGED */,
int dtr /* = SET */,
int rts /* = SET */,
int xon_xoff /* = DISABLE */,
int rts_cts /* = DISABLE */,
int dtr_dsr /* = DISABLE */ )
: m_TxQueue( MAX_OUTPUT_BUFFER_SIZE ),
m_RxQueue( MAX_INPUT_BUFFER_SIZE )
{
//
// Win32Port has to share the debug output with the parent class.
// To determine where our first line starts, we call the
// FormatDebugOutput() function from our parent class.
//
first_debug_output_line = RS232::FormatDebugOutput();
debug_line_count = FormatDebugOutput();
string temp = port;
//
// One of the base class members holds the port_name, which for
// most other class implemenations is a value ranging from COM1
// to COM9. Although not every port passed to this class conforms
// to that naming structure, most of them do. We try to extract
// that port name from the one passed in wherever possible. If no
// possible match is made, the value -1 is inserted into the
// port_name member. The only important place where the port_name
// member is used is in the dump status output routine of the
// base class. Initializing it here ensures that the value
// displayed in the debug output will usually match up with the
// value the calling program passed as a port name.
//
if ( temp.substr( 0, 4 ) == "\\\\.\\" )
temp = temp.substr( 4, string::npos );
if ( toupper( temp[ 0 ] ) == 'C' &&
toupper( temp[ 1 ] ) == 'O' &&
toupper( temp[ 2 ] ) == 'M' ) {
temp = temp.substr( 3, string::npos );
port_name = (RS232PortName) ( atoi( temp.c_str() ) - 1 );
} else
port_name = (RS232PortName) -1;
//
// Here is where the real work starts. We open the port using the
// flags appropriate to a serial port. Using FILE_FLAG_OVERLAPPED
// is critical, because our input and output threads depend on
// the asynchronous capabilities of Win32.
//
m_hPort = CreateFile( port.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
0 );
//
// If I get a valid handle, it means the port could at least be
// opened. I then try to set a bunch of miscellaneous things that
// the port needs to run properly. If everything goes according to
// plan, when that is all done I start the input and output threads
// and the port is then really running.
//
if ( m_hPort != INVALID_HANDLE_VALUE ) {
m_dwErrors = 0; //Clear cumulative line status errors
m_iBreakDuration = 0; //No break in progress, initialize to 0
SetLastError( 0 ); //Clear any Win32 error from this thread
read_settings(); //Read and save current port settings
saved_settings = settings; //Only needed because base class dumps
//the saved settings in debug output
//Init timeous to ensure our overlapped reads work
COMMTIMEOUTS timeouts = { 0x01, 0, 0, 0, 0 };
SetCommTimeouts( m_hPort, &timeouts );
SetupComm( m_hPort, 500, 500 ); //set buffer sizes
error_status = RS232_SUCCESS; //clear current class error
settings.Dtr = 1; //Set these five values to their
settings.Rts = 1; //default values, the Adjust()
settings.XonXoff = 0; //function will modify them if
settings.RtsCts = 0; //new values were passed in the args
settings.DtrDsr = 0; //to the constructor
settings.Adjust( baud_rate,
parity,
word_length,
stop_bits,
dtr,
rts,
xon_xoff,
rts_cts,
dtr_dsr );
//
// Now write the new settings to the port. If this or any other
// operation has failed, we abort here. The user will be able
// to see that the port is not working due to having an error
// set immediately upon opening.
//
error_status = write_settings();
if ( error_status != RS232_SUCCESS ) {
CloseHandle( m_hPort );
m_hPort = 0;
} else {
//
// Since the port opened properly, we're ready to start the
// input and output threads. Before they start we create the
// five Win32 events that will be used to pass requests to
// the threads. Note that the only argument passed to the
// thread initialization is a pointer to this. The thread
// needs that to find all of the data in the Win32Port
// object that it will be manipulating.
//
m_hKillInputThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
m_hKillOutputThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
m_hWriteRequestEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
m_hReadRequestEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
m_hBreakRequestEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
m_hInputThread = _beginthread( InputThread, 0, (void *) this );
m_hOutputThread = _beginthread( OutputThread, 0, (void *) this );
}
}
//
// If the CreateFile() function didn't succeed, we're really in a bad
// state. Just translate any error into something intellible and
// exit. The user will have tofigure out that the open failed due to
// the bad error_status member.
//
else
translate_last_error();
}
//
// The destructur only has a couple of important things to do. If
// the port was up and running, the handle that was created as a
// result of the call to CreateFile() has to be closed with a
// call to CloseHandle(). The two worker threads created when the
// port was opened both have to be killed, which is done by
// setting an Event that the threads will be continually watching
// for. Once that is complete, the five events created in the
// ctor can be destroyed as well.
//
// Note that because of the way this dtor works, we are
// vulnerable to getting hung up if one of the two worker threads
// doesn't exit properly. This is a strong incentive towards
// keeping the code in the threads nice and simple.
//
Win32Port::~Win32Port()
{
if ( m_hPort != INVALID_HANDLE_VALUE ) {
SetEvent( m_hKillOutputThreadEvent );
SetEvent( m_hKillInputThreadEvent );
long handles[ 2 ] = { m_hInputThread, m_hOutputThread };
WaitForMultipleObjects( 2,
(HANDLE *) handles,
TRUE,
INFINITE );
CloseHandle( m_hPort );
m_hPort = INVALID_HANDLE_VALUE;
CloseHandle( m_hKillInputThreadEvent );
CloseHandle( m_hKillOutputThreadEvent );
CloseHandle( m_hWriteRequestEvent );
CloseHandle( m_hReadRequestEvent );
CloseHandle( m_hBreakRequestEvent );
}
}
//
// The structure of the RS232 class called for a single global
// function that was called when the library was sitting in a
// loop waiting for input data to show up. Under Win32, there are
// a couple of different appropaches you might want to take when
// implementing this routine. If you are calling the routine from
// your main thread, you want to make sure that your GUI is
// getting some CPU cycles, so you probably want to replace this
// function with a version that operates your message pump. The
// routine might also check for a cancel flag that a user can set
// from inside your app.
//
// If you are calling this routine from a background thread, you
// could conceivably have it sleep for 5 or 10 milliseconds each
// time it is called. If you are actually idle and truly waiting
// for data to either arrive or be sent, it shouldn't cause any
// degradation of your app's throughput, and it will free up time
// for other processes and threads.
//
// Since this is a virtual function, you can override it in your
// derived class and use some object specific data to determine
// how it behaves.
//
int RS232::IdleFunction( void )
{
return RS232_SUCCESS;
}
//
// The RS232 base class expects a global ReadTime() function that
// returns the current time in milliseconds. This matches up well
// with the GetTickCount() in the Windows API.
//
long ReadTime( void )
{
return GetTickCount();
}
//
// This is our class specific implementation of the RS232 virtual
// function called Set(). It is called to set the four principal
// attributes of the serial port: baud rate, parity, word size,
// and number of stop bits. It relies on two internal functions
// to actually do all of the work. Member function Adjust() is
// called to accept the new settings and store them in the
// internal settings member. write_settings() is then called to
// take those values, make any modifications needed to the DCB,
// then call the Win32 API functions needed to update the
// physical values in the port. Illegal values can result in the
// error_status member being set to a bad value, which will
// prevent the port from working at all.
//
RS232Error Win32Port::Set( long baud_rate /* = UNCHANGED */,
int parity /* = UNCHANGED */,
int word_length /* = UNCHANGED */,
int stop_bits /* = UNCHANGED */ )
{
settings.Adjust( baud_rate,
parity,
word_length,
stop_bits,
UNCHANGED,
UNCHANGED,
UNCHANGED,
UNCHANGED,
UNCHANGED );
return write_settings();
}
//
// This is an implementation of the virtual Dtr() function from
// the base class RS232. It doesn't have to do too much work, it
// merely updates the settings member and then calls the
// write_settings() member function to do all the work. It does
// check to see if DTR/DSR handshaking is in place, and if it is,
// it returns the non-fatal warning to the calling routine. If
// that doesn't happen, it updates the Dtr member in the settings
// object and calls write_settings() to update the physical port.
// Note that it returns the current setting of the Dtr member. If
// this function is called with no arguments by the end user, it
// doesn't do anything except return that value.
//
int Win32Port::Dtr( int setting /* = UNCHANGED */ )
{
if ( error_status < RS232_SUCCESS )
return error_status;
if ( setting != UNCHANGED ) {
if ( settings.DtrDsr == 1 )
return WIN32_HANDSHAKE_LINE_IN_USE;
settings.Dtr = setting != 0;
RS232Error error = write_settings();
if ( error < RS232_SUCCESS )
return error;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -