|
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*=============================================================================
**
** Class: SerialStream
**
** Purpose: Class for enabling low-level sync and async control over a serial
** : communications resource.
**
** Date: August, 2002
**
=============================================================================*/
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Resources;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
// Notes about the SerialStream:
// * The stream is always opened via the SerialStream constructor.
// * Lifetime of the COM port's handle is controlled via a SafeHandle. Thus, all properties are available
// * only when the SerialStream is open and not disposed.
// * Handles to serial communications resources here always:
// * 1) own the handle
// * 2) are opened for asynchronous operation
// * 3) set access at the level of FileAccess.ReadWrite
// * 4) Allow for reading AND writing
// * 5) Disallow seeking, since they encapsulate a file of type FILE_TYPE_CHAR
namespace System.IO.Ports
{
internal sealed partial class SerialStream : Stream
{
const int errorEvents = (int) (SerialError.Frame | SerialError.Overrun |
SerialError.RXOver | SerialError.RXParity | SerialError.TXFull);
const int receivedEvents = (int) (SerialData.Chars | SerialData.Eof);
const int pinChangedEvents = (int) (SerialPinChange.Break | SerialPinChange.CDChanged | SerialPinChange.CtsChanged |
SerialPinChange.Ring | SerialPinChange.DsrChanged);
const int infiniteTimeoutConst = -2;
// members supporting properties exposed to SerialPort
private string portName;
private byte parityReplace = (byte) '?';
private bool inBreak = false; // port is initially in non-break state
private bool isAsync = true;
private Handshake handshake;
private bool rtsEnable = false;
// The internal C# representations of Win32 structures necessary for communication
// hold most of the internal "fields" maintaining information about the port.
private UnsafeNativeMethods.DCB dcb;
private UnsafeNativeMethods.COMMTIMEOUTS commTimeouts;
private UnsafeNativeMethods.COMSTAT comStat;
private UnsafeNativeMethods.COMMPROP commProp;
// internal-use members
// private const long dsrTimeout = 0L; -- Not used anymore.
private const int maxDataBits = 8;
private const int minDataBits = 5;
internal SafeFileHandle _handle = null;
internal EventLoopRunner eventRunner;
private byte[] tempBuf; // used to avoid multiple array allocations in ReadByte()
// called whenever any async i/o operation completes.
private unsafe static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(SerialStream.AsyncFSCallback);
// three different events, also wrapped by SerialPort.
internal event SerialDataReceivedEventHandler DataReceived; // called when one character is received.
internal event SerialPinChangedEventHandler PinChanged; // called when any of the pin/ring-related triggers occurs
internal event SerialErrorReceivedEventHandler ErrorReceived; // called when any runtime error occurs on the port (frame, overrun, parity, etc.)
// ----SECTION: inherited properties from Stream class ------------*
// These six properites are required for SerialStream to inherit from the abstract Stream class.
// Note four of them are always true or false, and two of them throw exceptions, so these
// are not usefully queried by applications which know they have a SerialStream, etc...
public override bool CanRead
{
get { return (_handle != null); }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanTimeout {
get { return (_handle != null); }
}
public override bool CanWrite
{
get { return (_handle != null); }
}
public override long Length
{
get { throw new NotSupportedException(SR.GetString(SR.NotSupported_UnseekableStream)); }
}
public override long Position
{
get { throw new NotSupportedException(SR.GetString(SR.NotSupported_UnseekableStream)); }
set { throw new NotSupportedException(SR.GetString(SR.NotSupported_UnseekableStream)); }
}
// ----- new get-set properties -----------------*
// Standard port properties, also called from SerialPort
// BaudRate may not be settable to an arbitrary integer between dwMinBaud and dwMaxBaud,
// and is limited only by the serial driver. Typically about twelve values such
// as Winbase.h's CBR_110 through CBR_256000 are used.
internal int BaudRate
{
//get { return (int) dcb.BaudRate; }
set
{
if (value <= 0 || (value > commProp.dwMaxBaud && commProp.dwMaxBaud > 0))
{
// if no upper bound on baud rate imposed by serial driver, note that argument must be positive
if (commProp.dwMaxBaud == 0)
{
throw new ArgumentOutOfRangeException("baudRate",
SR.GetString(SR.ArgumentOutOfRange_NeedPosNum));
}
else
{
// otherwise, we can present the bounds on the baud rate for this driver
throw new ArgumentOutOfRangeException("baudRate",
SR.GetString(SR.ArgumentOutOfRange_Bounds_Lower_Upper, 0, commProp.dwMaxBaud));
}
}
// Set only if it's different. Rollback to previous values if setting fails.
// This pattern occurs through most of the other properties in this class.
if(value != dcb.BaudRate)
{
int baudRateOld = (int) dcb.BaudRate;
dcb.BaudRate = (uint) value;
if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
{
dcb.BaudRate = (uint) baudRateOld;
InternalResources.WinIOError();
}
}
}
}
public bool BreakState {
get { return inBreak; }
set {
if (value) {
if (UnsafeNativeMethods.SetCommBreak(_handle) == false)
InternalResources.WinIOError();
inBreak = true;
}
else {
if (UnsafeNativeMethods.ClearCommBreak(_handle) == false)
InternalResources.WinIOError();
inBreak = false;
}
}
}
internal int DataBits
{
//get { return (int) dcb.ByteSize; }
set
{
Debug.Assert(!(value < minDataBits || value > maxDataBits), "An invalid value was passed to DataBits");
if (value != dcb.ByteSize)
{
byte byteSizeOld = dcb.ByteSize;
dcb.ByteSize = (byte) value;
if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
{
dcb.ByteSize = byteSizeOld;
InternalResources.WinIOError();
}
}
}
}
internal bool DiscardNull
{
//get { return (GetDcbFlag(NativeMethods.FNULL) == 1);}
set
{
int fNullFlag = GetDcbFlag(NativeMethods.FNULL);
if(value == true && fNullFlag == 0 || value == false && fNullFlag == 1)
{
int fNullOld = fNullFlag;
SetDcbFlag(NativeMethods.FNULL, value ? 1 : 0);
if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
{
SetDcbFlag(NativeMethods.FNULL, fNullOld);
InternalResources.WinIOError();
}
}
}
}
internal bool DtrEnable
{
get {
int fDtrControl = GetDcbFlag(NativeMethods.FDTRCONTROL);
return (fDtrControl == NativeMethods.DTR_CONTROL_ENABLE);
}
set
{
// first set the FDTRCONTROL field in the DCB struct
int fDtrControlOld = GetDcbFlag(NativeMethods.FDTRCONTROL);
SetDcbFlag(NativeMethods.FDTRCONTROL, value ? NativeMethods.DTR_CONTROL_ENABLE : NativeMethods.DTR_CONTROL_DISABLE);
if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
{
SetDcbFlag(NativeMethods.FDTRCONTROL, fDtrControlOld);
InternalResources.WinIOError();
}
// then set the actual pin
if (!UnsafeNativeMethods.EscapeCommFunction(_handle, value ? NativeMethods.SETDTR : NativeMethods.CLRDTR))
InternalResources.WinIOError();
}
}
internal Handshake Handshake
{
//get { return handshake; }
set
{
Debug.Assert(!(value < System.IO.Ports.Handshake.None || value > System.IO.Ports.Handshake.RequestToSendXOnXOff),
"An invalid value was passed to Handshake");
if(value != handshake)
{
// in the DCB, handshake affects the fRtsControl, fOutxCtsFlow, and fInX, fOutX fields,
// so we must save everything in that closure before making any changes.
Handshake handshakeOld = handshake;
int fInOutXOld = GetDcbFlag(NativeMethods.FINX);
int fOutxCtsFlowOld = GetDcbFlag(NativeMethods.FOUTXCTSFLOW);
int fRtsControlOld = GetDcbFlag(NativeMethods.FRTSCONTROL);
handshake = value;
int fInXOutXFlag = (handshake == Handshake.XOnXOff || handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0;
SetDcbFlag(NativeMethods.FINX, fInXOutXFlag);
SetDcbFlag(NativeMethods.FOUTX, fInXOutXFlag);
SetDcbFlag(NativeMethods.FOUTXCTSFLOW, (handshake == Handshake.RequestToSend ||
handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0);
if ((handshake == Handshake.RequestToSend ||
handshake == Handshake.RequestToSendXOnXOff))
{
SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_HANDSHAKE);
}
else if (rtsEnable)
{
SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_ENABLE);
}
else {
SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_DISABLE);
}
if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
{
handshake = handshakeOld;
SetDcbFlag(NativeMethods.FINX, fInOutXOld);
SetDcbFlag(NativeMethods.FOUTX, fInOutXOld);
SetDcbFlag(NativeMethods.FOUTXCTSFLOW, fOutxCtsFlowOld);
SetDcbFlag(NativeMethods.FRTSCONTROL, fRtsControlOld);
InternalResources.WinIOError();
}
}
}
}
internal bool IsOpen {
get {
return _handle != null && !eventRunner.ShutdownLoop;
}
}
internal Parity Parity
{
//get { return (Parity) dcb.Parity; }
set
{
Debug.Assert(!(value < Parity.None || value > Parity.Space), "An invalid value was passed to Parity");
if((byte) value != dcb.Parity)
{
byte parityOld = dcb.Parity;
// in the DCB structure, the parity setting also potentially effects:
// fParity, fErrorChar, ErrorChar
// so these must be saved as well.
int fParityOld = GetDcbFlag(NativeMethods.FPARITY);
byte ErrorCharOld = dcb.ErrorChar;
int fErrorCharOld = GetDcbFlag(NativeMethods.FERRORCHAR);
dcb.Parity = (byte) value;
int parityFlag = (dcb.Parity == (byte) Parity.None) ? 0 : 1;
SetDcbFlag(NativeMethods.FPARITY, parityFlag);
if (parityFlag == 1)
{
SetDcbFlag(NativeMethods.FERRORCHAR, (parityReplace != '\0') ? 1 : 0);
dcb.ErrorChar = parityReplace;
}
else
{
SetDcbFlag(NativeMethods.FERRORCHAR, 0);
dcb.ErrorChar = (byte) '\0';
}
if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
{
dcb.Parity = parityOld;
SetDcbFlag(NativeMethods.FPARITY, fParityOld);
dcb.ErrorChar = ErrorCharOld;
SetDcbFlag(NativeMethods.FERRORCHAR, fErrorCharOld);
InternalResources.WinIOError();
}
}
}
}
// ParityReplace is the eight-bit character which replaces any bytes which
// ParityReplace affects the equivalent field in the DCB structure: ErrorChar, and
// the DCB flag fErrorChar.
internal byte ParityReplace
{
//get { return parityReplace; }
set
{
if(value != parityReplace)
{
byte parityReplaceOld = parityReplace;
byte errorCharOld = dcb.ErrorChar;
int fErrorCharOld = GetDcbFlag(NativeMethods.FERRORCHAR);
parityReplace = value;
if (GetDcbFlag(NativeMethods.FPARITY) == 1)
{
SetDcbFlag(NativeMethods.FERRORCHAR, (parityReplace != '\0')? 1 : 0);
dcb.ErrorChar = parityReplace;
}
else
{
SetDcbFlag(NativeMethods.FERRORCHAR, 0);
dcb.ErrorChar = (byte) '\0';
}
if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
{
parityReplace = parityReplaceOld;
SetDcbFlag(NativeMethods.FERRORCHAR, fErrorCharOld);
dcb.ErrorChar = errorCharOld;
InternalResources.WinIOError();
}
}
}
}
// Timeouts are considered to be TOTAL time for the Read/Write operation and to be in milliseconds.
// Timeouts are translated into DCB structure as follows:
// Desired timeout => ReadTotalTimeoutConstant ReadTotalTimeoutMultiplier ReadIntervalTimeout
// 0 0 0 MAXDWORD
// 0 < n < infinity n MAXDWORD MAXDWORD
// infinity infiniteTimeoutConst MAXDWORD MAXDWORD
//
// rationale for "infinity": There does not exist in the COMMTIMEOUTS structure a way to
// *wait indefinitely for any byte, return when found*. Instead, if we set ReadTimeout
// to infinity, SerialStream's EndRead loops if infiniteTimeoutConst mills have elapsed
// without a byte received. Note that this is approximately 24 days, so essentially
// most practical purposes effectively equate 24 days with an infinite amount of time
// on a serial port connection.
public override int ReadTimeout
{
get
{
int constant = commTimeouts.ReadTotalTimeoutConstant;
if (constant == infiniteTimeoutConst) return SerialPort.InfiniteTimeout;
else return constant;
}
set
{
if (value < 0 && value != SerialPort.InfiniteTimeout)
throw new ArgumentOutOfRangeException("ReadTimeout", SR.GetString(SR.ArgumentOutOfRange_Timeout));
if (_handle == null) InternalResources.FileNotOpen();
int oldReadConstant = commTimeouts.ReadTotalTimeoutConstant;
int oldReadInterval = commTimeouts.ReadIntervalTimeout;
int oldReadMultipler = commTimeouts.ReadTotalTimeoutMultiplier;
// NOTE: this logic should match what is in the constructor
if (value == 0) {
commTimeouts.ReadTotalTimeoutConstant = 0;
commTimeouts.ReadTotalTimeoutMultiplier = 0;
commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD;
} else if (value == SerialPort.InfiniteTimeout) {
// SetCommTimeouts doesn't like a value of -1 for some reason, so
// we'll use -2(infiniteTimeoutConst) to represent infinite.
commTimeouts.ReadTotalTimeoutConstant = infiniteTimeoutConst;
commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD;
commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD;
} else {
commTimeouts.ReadTotalTimeoutConstant = value;
commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD;
commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD;
}
if (UnsafeNativeMethods.SetCommTimeouts(_handle, ref commTimeouts) == false)
{
commTimeouts.ReadTotalTimeoutConstant = oldReadConstant;
commTimeouts.ReadTotalTimeoutMultiplier = oldReadMultipler;
commTimeouts.ReadIntervalTimeout = oldReadInterval;
InternalResources.WinIOError();
}
}
}
internal bool RtsEnable
{
get {
int fRtsControl = GetDcbFlag(NativeMethods.FRTSCONTROL);
if (fRtsControl == NativeMethods.RTS_CONTROL_HANDSHAKE)
throw new InvalidOperationException(SR.GetString(SR.CantSetRtsWithHandshaking));
return (fRtsControl == NativeMethods.RTS_CONTROL_ENABLE);
}
set
{
if ((handshake == Handshake.RequestToSend || handshake == Handshake.RequestToSendXOnXOff))
throw new InvalidOperationException(SR.GetString(SR.CantSetRtsWithHandshaking));
if (value != rtsEnable) {
int fRtsControlOld = GetDcbFlag(NativeMethods.FRTSCONTROL);
rtsEnable = value;
if(value)
SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_ENABLE);
else
SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_DISABLE);
if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
{
SetDcbFlag(NativeMethods.FRTSCONTROL, fRtsControlOld);
// set it back to the old value on a failure
rtsEnable = !rtsEnable;
InternalResources.WinIOError();
}
if (!UnsafeNativeMethods.EscapeCommFunction(_handle, value ? NativeMethods.SETRTS : NativeMethods.CLRRTS))
InternalResources.WinIOError();
}
}
}
// StopBits represented in C# as StopBits enum type and in Win32 as an integer 1, 2, or 3.
internal StopBits StopBits
{
/*get
{
switch(dcb.StopBits)
{
case NativeMethods.ONESTOPBIT:
return StopBits.One;
case NativeMethods.ONE5STOPBITS:
return StopBits.OnePointFive;
case NativeMethods.TWOSTOPBITS:
return StopBits.Two;
default:
Debug.Assert(true, "Invalid Stopbits value " + dcb.StopBits);
return StopBits.One;
}
}
*/
set
{
Debug.Assert(!(value < StopBits.One || value > StopBits.OnePointFive), "An invalid value was passed to StopBits");
byte nativeValue = 0;
if (value == StopBits.One) nativeValue = (byte) NativeMethods.ONESTOPBIT;
else if (value == StopBits.OnePointFive) nativeValue = (byte) NativeMethods.ONE5STOPBITS;
else nativeValue = (byte) NativeMethods.TWOSTOPBITS;
if(nativeValue != dcb.StopBits)
{
byte stopBitsOld = dcb.StopBits;
dcb.StopBits = nativeValue;
if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
{
dcb.StopBits = stopBitsOld;
InternalResources.WinIOError();
}
}
}
}
// note: WriteTimeout must be either SerialPort.InfiniteTimeout or POSITIVE.
// a timeout of zero implies that every Write call throws an exception.
public override int WriteTimeout
{
get
{
int timeout = commTimeouts.WriteTotalTimeoutConstant;
return (timeout == 0) ? SerialPort.InfiniteTimeout : timeout;
}
set
{
if (value <= 0 && value != SerialPort.InfiniteTimeout)
throw new ArgumentOutOfRangeException("WriteTimeout", SR.GetString(SR.ArgumentOutOfRange_WriteTimeout));
if (_handle == null) InternalResources.FileNotOpen();
int oldWriteConstant = commTimeouts.WriteTotalTimeoutConstant;
commTimeouts.WriteTotalTimeoutConstant = ((value == SerialPort.InfiniteTimeout) ? 0 : value);
if (UnsafeNativeMethods.SetCommTimeouts(_handle, ref commTimeouts) == false)
{
commTimeouts.WriteTotalTimeoutConstant = oldWriteConstant;
InternalResources.WinIOError();
}
}
}
// CDHolding, CtsHolding, DsrHolding query the current state of each of the carrier, the CTS pin,
// and the DSR pin, respectively. Read-only.
// All will throw exceptions if the port is not open.
internal bool CDHolding
{
get
{
int pinStatus = 0;
if (UnsafeNativeMethods.GetCommModemStatus(_handle, ref pinStatus) == false)
InternalResources.WinIOError();
return (NativeMethods.MS_RLSD_ON & pinStatus) != 0;
}
}
internal bool CtsHolding
{
get
{
int pinStatus = 0;
if (UnsafeNativeMethods.GetCommModemStatus(_handle, ref pinStatus) == false)
InternalResources.WinIOError();
return (NativeMethods.MS_CTS_ON & pinStatus) != 0;
}
}
internal bool DsrHolding
{
get
{
int pinStatus = 0;
if (UnsafeNativeMethods.GetCommModemStatus(_handle, ref pinStatus) == false)
InternalResources.WinIOError();
return (NativeMethods.MS_DSR_ON & pinStatus) != 0;
}
}
// Fills comStat structure from an unmanaged function
// to determine the number of bytes waiting in the serial driver's internal receive buffer.
internal int BytesToRead {
get
{
int errorCode = 0; // "ref" arguments need to have values, as opposed to "out" arguments
if (UnsafeNativeMethods.ClearCommError(_handle, ref errorCode, ref comStat) == false)
{
InternalResources.WinIOError();
}
return (int) comStat.cbInQue;
}
}
// Fills comStat structure from an unmanaged function
// to determine the number of bytes waiting in the serial driver's internal transmit buffer.
internal int BytesToWrite {
get
{
int errorCode = 0; // "ref" arguments need to be set before method invocation, as opposed to "out" arguments
if (UnsafeNativeMethods.ClearCommError(_handle, ref errorCode, ref comStat) == false)
InternalResources.WinIOError();
return (int) comStat.cbOutQue;
}
}
// -----------SECTION: constructor --------------------------*
// this method is used by SerialPort upon SerialStream's creation
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
internal SerialStream(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int readTimeout, int writeTimeout, Handshake handshake,
bool dtrEnable, bool rtsEnable, bool discardNull, byte parityReplace)
{
int flags = UnsafeNativeMethods.FILE_FLAG_OVERLAPPED;
// disable async on win9x
if (Environment.OSVersion.Platform == PlatformID.Win32Windows) {
flags = UnsafeNativeMethods.FILE_ATTRIBUTE_NORMAL;
isAsync = false;
}
if ((portName == null) || !portName.StartsWith("COM", StringComparison.OrdinalIgnoreCase))
throw new ArgumentException(SR.GetString(SR.Arg_InvalidSerialPort), "portName");
//Error checking done in SerialPort.
SafeFileHandle tempHandle = UnsafeNativeMethods.CreateFile("\\\\.\\" + portName,
NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
0, // comm devices must be opened w/exclusive-access
IntPtr.Zero, // no security attributes
UnsafeNativeMethods.OPEN_EXISTING, // comm devices must use OPEN_EXISTING
flags,
IntPtr.Zero // hTemplate must be NULL for comm devices
);
if (tempHandle.IsInvalid)
{
InternalResources.WinIOError(portName);
}
try {
int fileType = UnsafeNativeMethods.GetFileType(tempHandle);
// Allowing FILE_TYPE_UNKNOWN for legitimate serial device such as USB to serial adapter device
if ((fileType != UnsafeNativeMethods.FILE_TYPE_CHAR) && (fileType != UnsafeNativeMethods.FILE_TYPE_UNKNOWN))
throw new ArgumentException(SR.GetString(SR.Arg_InvalidSerialPort), "portName");
_handle = tempHandle;
// set properties of the stream that exist as members in SerialStream
this.portName = portName;
this.handshake = handshake;
this.parityReplace = parityReplace;
tempBuf = new byte[1]; // used in ReadByte()
// Fill COMMPROPERTIES struct, which has our maximum allowed baud rate.
// Call a serial specific API such as GetCommModemStatus which would fail
// in case the device is not a legitimate serial device. For instance,
// some illegal FILE_TYPE_UNKNOWN device (or) "LPT1" on Win9x
// trying to pass for serial will be caught here. GetCommProperties works
// fine for "LPT1" on Win9x, so that alone can't be relied here to
// detect non serial devices.
commProp = new UnsafeNativeMethods.COMMPROP();
int pinStatus = 0;
if (!UnsafeNativeMethods.GetCommProperties(_handle, ref commProp)
|| !UnsafeNativeMethods.GetCommModemStatus(_handle, ref pinStatus))
{
// If the portName they have passed in is a FILE_TYPE_CHAR but not a serial port,
// for example "LPT1", this API will fail. For this reason we handle the error message specially.
int errorCode = Marshal.GetLastWin32Error();
if ((errorCode == NativeMethods.ERROR_INVALID_PARAMETER) || (errorCode == NativeMethods.ERROR_INVALID_HANDLE))
throw new ArgumentException(SR.GetString(SR.Arg_InvalidSerialPortExtended), "portName");
else
InternalResources.WinIOError(errorCode, string.Empty);
}
if (commProp.dwMaxBaud != 0 && baudRate > commProp.dwMaxBaud)
throw new ArgumentOutOfRangeException("baudRate", SR.GetString(SR.Max_Baud, commProp.dwMaxBaud));
comStat = new UnsafeNativeMethods.COMSTAT();
// create internal DCB structure, initialize according to Platform SDK
// standard: ms-help://MS.MSNDNQTR.2002APR.1003/hardware/commun_965u.htm
dcb = new UnsafeNativeMethods.DCB();
// set constant properties of the DCB
InitializeDCB(baudRate, parity, dataBits, stopBits, discardNull);
this.DtrEnable = dtrEnable;
// query and cache the initial RtsEnable value
// so that set_RtsEnable can do the (value != rtsEnable) optimization
this.rtsEnable = (GetDcbFlag(NativeMethods.FRTSCONTROL) == NativeMethods.RTS_CONTROL_ENABLE);
// now set this.RtsEnable to the specified value.
// Handshake takes precedence, this will be a nop if
// handshake is either RequestToSend or RequestToSendXOnXOff
if ((handshake != Handshake.RequestToSend && handshake != Handshake.RequestToSendXOnXOff))
this.RtsEnable = rtsEnable;
// NOTE: this logic should match what is in the ReadTimeout property
if (readTimeout == 0) {
commTimeouts.ReadTotalTimeoutConstant = 0;
commTimeouts.ReadTotalTimeoutMultiplier = 0;
commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD;
} else if (readTimeout == SerialPort.InfiniteTimeout) {
// SetCommTimeouts doesn't like a value of -1 for some reason, so
// we'll use -2(infiniteTimeoutConst) to represent infinite.
commTimeouts.ReadTotalTimeoutConstant = infiniteTimeoutConst;
commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD;
commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD;
} else {
commTimeouts.ReadTotalTimeoutConstant = readTimeout;
commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD;
commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD;
}
commTimeouts.WriteTotalTimeoutMultiplier = 0;
commTimeouts.WriteTotalTimeoutConstant = ((writeTimeout == SerialPort.InfiniteTimeout) ? 0 : writeTimeout);
// set unmanaged timeout structure
if (UnsafeNativeMethods.SetCommTimeouts(_handle, ref commTimeouts) == false)
{
InternalResources.WinIOError();
}
if (isAsync) {
if (!ThreadPool.BindHandle(_handle))
{
throw new IOException(SR.GetString(SR.IO_BindHandleFailed));
}
}
// monitor all events except TXEMPTY
UnsafeNativeMethods.SetCommMask(_handle, NativeMethods.ALL_EVENTS);
// prep. for starting event cycle.
eventRunner = new EventLoopRunner(this);
Thread eventLoopThread = LocalAppContextSwitches.DoNotCatchSerialStreamThreadExceptions
? new Thread(new ThreadStart(eventRunner.WaitForCommEvent))
: new Thread(new ThreadStart(eventRunner.SafelyWaitForCommEvent));
eventLoopThread.IsBackground = true;
eventLoopThread.Start();
}
catch {
// if there are any exceptions after the call to CreateFile, we need to be sure to close the
// handle before we let them continue up.
tempHandle.Close();
_handle = null;
throw;
}
}
~SerialStream()
{
Dispose(false);
}
protected override void Dispose(bool disposing)
{
// Signal the other side that we're closing. Should do regardless of whether we've called
// Close() or not Dispose()
if (_handle != null && !_handle.IsInvalid) {
try {
eventRunner.endEventLoop = true;
Thread.MemoryBarrier();
bool skipSPAccess = false;
// turn off all events and signal WaitCommEvent
UnsafeNativeMethods.SetCommMask(_handle, 0);
if (!UnsafeNativeMethods.EscapeCommFunction(_handle, NativeMethods.CLRDTR))
{
int hr = Marshal.GetLastWin32Error();
// access denied can happen if USB is yanked out. If that happens, we
// want to at least allow finalize to succeed and clean up everything
// we can. To achieve this, we need to avoid further attempts to access
// the SerialPort. A customer also reported seeing ERROR_BAD_COMMAND here.
// Do not throw an exception on the finalizer thread - that's just rude,
// since apps can't catch it and we may tear down the app.
const int ERROR_DEVICE_REMOVED = 1617;
if ((hr == NativeMethods.ERROR_ACCESS_DENIED || hr == NativeMethods.ERROR_BAD_COMMAND || hr == ERROR_DEVICE_REMOVED) && !disposing) {
skipSPAccess = true;
}
else {
// should not happen
Contract.Assert(false, String.Format("Unexpected error code from EscapeCommFunction in SerialPort.Dispose(bool) Error code: 0x{0:x}", (uint)hr));
// Do not throw an exception from the finalizer here.
if (disposing)
InternalResources.WinIOError();
}
}
if (!skipSPAccess && !_handle.IsClosed) {
Flush();
}
eventRunner.waitCommEventWaitHandle.Set();
if (!skipSPAccess) {
DiscardInBuffer();
DiscardOutBuffer();
}
if (disposing && eventRunner != null) {
// now we need to wait for the event loop to tell us it's done. Without this we could get into a ---- where the
// event loop kept the port open even after Dispose ended.
eventRunner.eventLoopEndedSignal.WaitOne();
eventRunner.eventLoopEndedSignal.Close();
eventRunner.waitCommEventWaitHandle.Close();
}
}
finally {
// If we are disposing synchronize closing with raising SerialPort events
if (disposing) {
lock (this) {
_handle.Close();
_handle = null;
}
}
else {
_handle.Close();
_handle = null;
}
base.Dispose(disposing);
}
}
}
// -----SECTION: all public methods ------------------*
// User-accessible async read method. Returns SerialStreamAsyncResult : IAsyncResult
[HostProtection(ExternalThreading=true)]
public override IAsyncResult BeginRead(byte[] array, int offset,int numBytes, AsyncCallback userCallback, object stateObject)
{
if (array==null)
throw new ArgumentNullException("array");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
if (numBytes < 0)
throw new ArgumentOutOfRangeException("numBytes", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
if (array.Length - offset < numBytes)
throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
if (_handle == null) InternalResources.FileNotOpen();
int oldtimeout = ReadTimeout;
ReadTimeout = SerialPort.InfiniteTimeout;
IAsyncResult result;
try {
if (!isAsync)
result = base.BeginRead(array, offset, numBytes, userCallback, stateObject);
else
result = BeginReadCore(array, offset, numBytes, userCallback, stateObject);
}
finally {
ReadTimeout = oldtimeout;
}
return result;
}
// User-accessible async write method. Returns SerialStreamAsyncResult : IAsyncResult
// Throws an exception if port is in break state.
[HostProtection(ExternalThreading=true)]
public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes,
AsyncCallback userCallback, object stateObject)
{
if (inBreak)
throw new InvalidOperationException(SR.GetString(SR.In_Break_State));
if (array==null)
throw new ArgumentNullException("array");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
if (numBytes < 0)
throw new ArgumentOutOfRangeException("numBytes", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
if (array.Length - offset < numBytes)
throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
if (_handle == null) InternalResources.FileNotOpen();
int oldtimeout = WriteTimeout;
WriteTimeout = SerialPort.InfiniteTimeout;
IAsyncResult result;
try {
if (!isAsync)
result = base.BeginWrite(array, offset, numBytes, userCallback, stateObject);
else
result = BeginWriteCore(array, offset, numBytes, userCallback, stateObject);
}
finally {
WriteTimeout = oldtimeout;
}
return result;
}
// Uses Win32 method to dump out the receive buffer; analagous to MSComm's "InBufferCount = 0"
internal void DiscardInBuffer()
{
if (UnsafeNativeMethods.PurgeComm(_handle, NativeMethods.PURGE_RXCLEAR | NativeMethods.PURGE_RXABORT) == false)
InternalResources.WinIOError();
}
// Uses Win32 method to dump out the xmit buffer; analagous to MSComm's "OutBufferCount = 0"
internal void DiscardOutBuffer()
{
if (UnsafeNativeMethods.PurgeComm(_handle, NativeMethods.PURGE_TXCLEAR | NativeMethods.PURGE_TXABORT) == false)
InternalResources.WinIOError();
}
// Async companion to BeginRead.
// Note, assumed IAsyncResult argument is of derived type SerialStreamAsyncResult,
// and throws an exception if untrue.
public unsafe override int EndRead(IAsyncResult asyncResult)
{
if (!isAsync)
return base.EndRead(asyncResult);
if (asyncResult==null)
throw new ArgumentNullException("asyncResult");
SerialStreamAsyncResult afsar = asyncResult as SerialStreamAsyncResult;
if (afsar==null || afsar._isWrite)
InternalResources.WrongAsyncResult();
// This sidesteps race conditions, avoids memory corruption after freeing the
// NativeOverlapped class or GCHandle twice.
if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0))
InternalResources.EndReadCalledTwice();
bool failed = false;
// Obtain the WaitHandle, but don't use public property in case we
// delay initialize the manual reset event in the future.
WaitHandle wh = afsar._waitHandle;
if (wh != null)
{
// We must block to ensure that AsyncFSCallback has completed,
// and we should close the WaitHandle in here.
try {
wh.WaitOne();
Debug.Assert(afsar._isComplete == true, "SerialStream::EndRead - AsyncFSCallback didn't set _isComplete to true!");
// InfiniteTimeout is not something native to the underlying serial device,
// we specify the timeout to be a very large value (MAXWORD-1) to achieve
// an infinite timeout illusion.
// I'm not sure what we can do here after an asyn operation with infinite
// timeout returns with no data. From a purist point of view we should
// somehow restart the read operation but we are not in a position to do so
// (and frankly that may not necessarily be the right thing to do here)
// I think the best option in this (almost impossible to run into) situation
// is to throw some sort of IOException.
if ((afsar._numBytes == 0) && (ReadTimeout == SerialPort.InfiniteTimeout) && (afsar._errorCode == 0))
failed = true;
}
finally {
wh.Close();
}
}
// Free memory, GC handles.
NativeOverlapped* overlappedPtr = afsar._overlapped;
if (overlappedPtr != null)
Overlapped.Free(overlappedPtr);
// Check for non-timeout errors during the read.
if (afsar._errorCode != 0)
InternalResources.WinIOError(afsar._errorCode, portName);
if (failed)
throw new IOException(SR.GetString(SR.IO_OperationAborted));
return afsar._numBytes;
}
// Async companion to BeginWrite.
// Note, assumed IAsyncResult argument is of derived type SerialStreamAsyncResult,
// and throws an exception if untrue.
// Also fails if called in port's break state.
public unsafe override void EndWrite(IAsyncResult asyncResult) {
if (!isAsync) {
base.EndWrite(asyncResult);
return;
}
if (inBreak)
throw new InvalidOperationException(SR.GetString(SR.In_Break_State));
if (asyncResult==null)
throw new ArgumentNullException("asyncResult");
SerialStreamAsyncResult afsar = asyncResult as SerialStreamAsyncResult;
if (afsar==null || !afsar._isWrite)
InternalResources.WrongAsyncResult();
// This sidesteps race conditions, avoids memory corruption after freeing the
// NativeOverlapped class or GCHandle twice.
if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0))
InternalResources.EndWriteCalledTwice();
// Obtain the WaitHandle, but don't use public property in case we
// delay initialize the manual reset event in the future.
WaitHandle wh = afsar._waitHandle;
if (wh != null)
{
// We must block to ensure that AsyncFSCallback has completed,
// and we should close the WaitHandle in here.
try {
wh.WaitOne();
Debug.Assert(afsar._isComplete == true, "SerialStream::EndWrite - AsyncFSCallback didn't set _isComplete to true!");
}
finally {
wh.Close();
}
}
// Free memory, GC handles.
NativeOverlapped* overlappedPtr = afsar._overlapped;
if (overlappedPtr != null)
Overlapped.Free(overlappedPtr);
// Now check for any error during the write.
if (afsar._errorCode != 0)
InternalResources.WinIOError(afsar._errorCode, portName);
// Number of bytes written is afsar._numBytes.
}
// Flush dumps the contents of the serial driver's internal read and write buffers.
// We actually expose the functionality for each, but fulfilling Stream's contract
// requires a Flush() method. Fails if handle closed.
// Note: Serial driver's write buffer is *already* attempting to write it, so we can only wait until it finishes.
public override void Flush()
{
if (_handle == null) throw new ObjectDisposedException(SR.GetString(SR.Port_not_open));
UnsafeNativeMethods.FlushFileBuffers(_handle);
}
// Blocking read operation, returning the number of bytes read from the stream.
public override int Read([In, Out] byte[] array, int offset, int count)
{
return Read(array, offset, count, ReadTimeout);
}
internal unsafe int Read([In, Out] byte[] array, int offset, int count, int timeout)
{
if (array==null)
throw new ArgumentNullException("array", SR.GetString(SR.ArgumentNull_Buffer));
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
if (count < 0)
throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
if (array.Length - offset < count)
throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
if (count == 0) return 0; // return immediately if no bytes requested; no need for overhead.
Debug.Assert(timeout == SerialPort.InfiniteTimeout || timeout >= 0, "Serial Stream Read - called with timeout " + timeout);
// Check to see we have no handle-related error, since the port's always supposed to be open.
if (_handle == null) InternalResources.FileNotOpen();
int numBytes = 0;
int hr;
if (isAsync) {
IAsyncResult result = BeginReadCore(array, offset, count, null, null);
numBytes = EndRead(result);
}
else {
numBytes = ReadFileNative(array, offset, count, null, out hr);
if (numBytes == -1) {
InternalResources.WinIOError();
}
}
if (numBytes == 0)
throw new TimeoutException();
return numBytes;
}
public override int ReadByte()
{
return ReadByte(ReadTimeout);
}
internal unsafe int ReadByte(int timeout)
{
if (_handle == null) InternalResources.FileNotOpen();
int numBytes = 0;
int hr;
if (isAsync) {
IAsyncResult result = BeginReadCore(tempBuf, 0, 1, null, null);
numBytes = EndRead(result);
}
else {
numBytes = ReadFileNative(tempBuf, 0, 1, null, out hr);
if (numBytes == -1) {
InternalResources.WinIOError();
}
}
if (numBytes == 0)
throw new TimeoutException();
else
return tempBuf[0];
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException(SR.GetString(SR.NotSupported_UnseekableStream));
}
public override void SetLength(long value)
{
throw new NotSupportedException(SR.GetString(SR.NotSupported_UnseekableStream));
}
internal void SetBufferSizes(int readBufferSize, int writeBufferSize) {
if (_handle == null) InternalResources.FileNotOpen();
if (!UnsafeNativeMethods.SetupComm(_handle, readBufferSize, writeBufferSize))
InternalResources.WinIOError();
}
public override void Write(byte[] array, int offset, int count)
{
Write(array, offset, count, WriteTimeout);
}
internal unsafe void Write(byte[] array, int offset, int count, int timeout)
{
if (inBreak)
throw new InvalidOperationException(SR.GetString(SR.In_Break_State));
if (array==null)
throw new ArgumentNullException("buffer", SR.GetString(SR.ArgumentNull_Array));
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedPosNum));
if (count < 0)
throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedPosNum));
if (count == 0) return; // no need to expend overhead in creating asyncResult, etc.
if (array.Length - offset < count)
throw new ArgumentException("count",SR.GetString(SR.ArgumentOutOfRange_OffsetOut));
Debug.Assert(timeout == SerialPort.InfiniteTimeout || timeout >= 0, "Serial Stream Write - write timeout is " + timeout);
// check for open handle, though the port is always supposed to be open
if (_handle == null) InternalResources.FileNotOpen();
int numBytes;
int hr;
if (isAsync) {
IAsyncResult result = BeginWriteCore(array, offset, count, null, null);
EndWrite(result);
SerialStreamAsyncResult afsar = result as SerialStreamAsyncResult;
Debug.Assert(afsar != null, "afsar should be a SerialStreamAsyncResult and should not be null");
numBytes = afsar._numBytes;
}
else {
numBytes = WriteFileNative(array, offset, count, null, out hr);
if (numBytes == -1) {
// This is how writes timeout on Win9x.
if (hr == NativeMethods.ERROR_COUNTER_TIMEOUT)
throw new TimeoutException(SR.GetString(SR.Write_timed_out));
InternalResources.WinIOError();
}
}
if (numBytes == 0)
throw new TimeoutException(SR.GetString(SR.Write_timed_out));
}
// use default timeout as argument to WriteByte override with timeout arg
public override void WriteByte(byte value)
{
WriteByte(value, WriteTimeout);
}
internal unsafe void WriteByte(byte value, int timeout)
{
if (inBreak)
throw new InvalidOperationException(SR.GetString(SR.In_Break_State));
if (_handle == null) InternalResources.FileNotOpen();
tempBuf[0] = value;
int numBytes;
int hr;
if (isAsync) {
IAsyncResult result = BeginWriteCore(tempBuf, 0, 1, null, null);
EndWrite(result);
SerialStreamAsyncResult afsar = result as SerialStreamAsyncResult;
Debug.Assert(afsar != null, "afsar should be a SerialStreamAsyncResult and should not be null");
numBytes = afsar._numBytes;
}
else {
numBytes = WriteFileNative(tempBuf, 0, 1, null, out hr);
if (numBytes == -1) {
// This is how writes timeout on Win9x.
if (Marshal.GetLastWin32Error() == NativeMethods.ERROR_COUNTER_TIMEOUT)
throw new TimeoutException(SR.GetString(SR.Write_timed_out));
InternalResources.WinIOError();
}
}
if (numBytes == 0)
throw new TimeoutException(SR.GetString(SR.Write_timed_out));
return;
}
// --------SUBSECTION: internal-use methods ----------------------*
// ------ internal DCB-supporting methods ------- *
// Initializes unmananged DCB struct, to be called after opening communications resource.
// assumes we have already: baudRate, parity, dataBits, stopBits
// should only be called in SerialStream(...)
private void InitializeDCB(int baudRate, Parity parity, int dataBits, StopBits stopBits, bool discardNull)
{
// first get the current dcb structure setup
if (UnsafeNativeMethods.GetCommState(_handle, ref dcb) == false)
{
InternalResources.WinIOError();
}
dcb.DCBlength = (uint) System.Runtime.InteropServices.Marshal.SizeOf(dcb);
// set parameterized properties
dcb.BaudRate = (uint) baudRate;
dcb.ByteSize = (byte) dataBits;
switch (stopBits)
{
case StopBits.One:
dcb.StopBits = NativeMethods.ONESTOPBIT;
break;
case StopBits.OnePointFive:
dcb.StopBits = NativeMethods.ONE5STOPBITS;
break;
case StopBits.Two:
dcb.StopBits = NativeMethods.TWOSTOPBITS;
break;
default:
Debug.Assert(false, "Invalid value for stopBits");
break;
}
dcb.Parity = (byte) parity;
// SetDcbFlag, GetDcbFlag expose access to each of the relevant bits of the 32-bit integer
// storing all flags of the DCB. C# provides no direct means of manipulating bit fields, so
// this is the solution.
SetDcbFlag(NativeMethods.FPARITY, ((parity == Parity.None) ? 0 : 1));
SetDcbFlag(NativeMethods.FBINARY, 1); // always true for communications resources
// set DCB fields implied by default and the arguments given.
// Boolean fields in C# must become 1, 0 to properly set the bit flags in the unmanaged DCB struct
SetDcbFlag(NativeMethods.FOUTXCTSFLOW, ((handshake == Handshake.RequestToSend ||
handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0));
// SetDcbFlag(NativeMethods.FOUTXDSRFLOW, (dsrTimeout != 0L) ? 1 : 0);
SetDcbFlag(NativeMethods.FOUTXDSRFLOW, 0); // dsrTimeout is always set to 0.
SetDcbFlag(NativeMethods.FDTRCONTROL, NativeMethods.DTR_CONTROL_DISABLE);
SetDcbFlag(NativeMethods.FDSRSENSITIVITY, 0); // this should remain off
SetDcbFlag(NativeMethods.FINX, (handshake == Handshake.XOnXOff || handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0);
SetDcbFlag(NativeMethods.FOUTX,(handshake == Handshake.XOnXOff || handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0);
// if no parity, we have no error character (i.e. ErrorChar = '\0' or null character)
if (parity != Parity.None)
{
SetDcbFlag(NativeMethods.FERRORCHAR, (parityReplace != '\0') ? 1 : 0);
dcb.ErrorChar = parityReplace;
}
else
{
SetDcbFlag(NativeMethods.FERRORCHAR, 0);
dcb.ErrorChar = (byte) '\0';
}
// this method only runs once in the constructor, so we only have the default value to use.
// Later the user may change this via the NullDiscard property.
SetDcbFlag(NativeMethods.FNULL, discardNull ? 1 : 0);
// Setting RTS control, which is RTS_CONTROL_HANDSHAKE if RTS / RTS-XOnXOff handshaking
// used, RTS_ENABLE (RTS pin used during operation) if rtsEnable true but XOnXoff / No handshaking
// used, and disabled otherwise.
if ((handshake == Handshake.RequestToSend ||
handshake == Handshake.RequestToSendXOnXOff))
{
SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_HANDSHAKE);
}
else if (GetDcbFlag(NativeMethods.FRTSCONTROL) == NativeMethods.RTS_CONTROL_HANDSHAKE)
{
SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_DISABLE);
}
dcb.XonChar = NativeMethods.DEFAULTXONCHAR; // may be exposed later but for now, constant
dcb.XoffChar = NativeMethods.DEFAULTXOFFCHAR;
// minimum number of bytes allowed in each buffer before flow control activated
// heuristically, this has been set at 1/4 of the buffer size
dcb.XonLim = dcb.XoffLim = (ushort) (commProp.dwCurrentRxQueue / 4);
dcb.EofChar = NativeMethods.EOFCHAR;
//OLD MSCOMM: dcb.EvtChar = (byte) 0;
// now changed to make use of RXFlag WaitCommEvent event => Eof WaitForCommEvent event
dcb.EvtChar = NativeMethods.EOFCHAR;
// set DCB structure
if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
{
InternalResources.WinIOError();
}
}
// Here we provide a method for getting the flags of the Device Control Block structure dcb
// associated with each instance of SerialStream, i.e. this method gets myStream.dcb.Flags
// Flags are any of the constants in NativeMethods such as FBINARY, FDTRCONTROL, etc.
internal int GetDcbFlag(int whichFlag)
{
uint mask;
Debug.Assert(whichFlag >= NativeMethods.FBINARY && whichFlag <= NativeMethods.FDUMMY2, "GetDcbFlag needs to fit into enum!");
if (whichFlag == NativeMethods.FDTRCONTROL || whichFlag == NativeMethods.FRTSCONTROL)
{
mask = 0x3;
}
else if (whichFlag == NativeMethods.FDUMMY2)
{
mask = 0x1FFFF;
}
else
{
mask = 0x1;
}
uint result = dcb.Flags & (mask << whichFlag);
return (int) (result >> whichFlag);
}
// Since C# applications have to provide a workaround for accessing and setting bitfields in unmanaged code,
// here we provide methods for getting and setting the Flags field of the Device Control Block structure dcb
// associated with each instance of SerialStream, i.e. this method sets myStream.dcb.Flags
// Flags are any of the constants in NativeMethods such as FBINARY, FDTRCONTROL, etc.
internal void SetDcbFlag(int whichFlag, int setting)
{
uint mask;
setting = setting << whichFlag;
Debug.Assert(whichFlag >= NativeMethods.FBINARY && whichFlag <= NativeMethods.FDUMMY2, "SetDcbFlag needs to fit into enum!");
if (whichFlag == NativeMethods.FDTRCONTROL || whichFlag == NativeMethods.FRTSCONTROL)
{
mask = 0x3;
}
else if (whichFlag == NativeMethods.FDUMMY2)
{
mask = 0x1FFFF;
}
else
{
mask = 0x1;
}
// clear the region
dcb.Flags &= ~(mask << whichFlag);
// set the region
dcb.Flags |= ((uint) setting);
}
// ----SUBSECTION: internal methods supporting public read/write methods-------*
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
unsafe private SerialStreamAsyncResult BeginReadCore(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)
{
// Create and store async stream class library specific data in the
// async result
SerialStreamAsyncResult asyncResult = new SerialStreamAsyncResult();
asyncResult._userCallback = userCallback;
asyncResult._userStateObject = stateObject;
asyncResult._isWrite = false;
// For Synchronous IO, I could go with either a callback and using
// the managed Monitor class, or I could create a handle and wait on it.
ManualResetEvent waitHandle = new ManualResetEvent(false);
asyncResult._waitHandle = waitHandle;
// Create a managed overlapped class
// We will set the file offsets later
Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, asyncResult);
// Pack the Overlapped class, and store it in the async result
NativeOverlapped* intOverlapped = overlapped.Pack(IOCallback, array);
asyncResult._overlapped = intOverlapped;
// queue an async ReadFile operation and pass in a packed overlapped
//int r = ReadFile(_handle, array, numBytes, null, intOverlapped);
int hr = 0;
int r = ReadFileNative(array, offset, numBytes,
intOverlapped, out hr);
// ReadFile, the OS version, will return 0 on failure. But
// my ReadFileNative wrapper returns -1. My wrapper will return
// the following:
// On error, r==-1.
// On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING
// on async requests that completed sequentially, r==0
// Note that you will NEVER RELIABLY be able to get the number of bytes
// read back from this call when using overlapped structures! You must
// not pass in a non-null lpNumBytesRead to ReadFile when using
// overlapped structures!
if (r==-1)
{
if (hr != NativeMethods.ERROR_IO_PENDING)
{
if (hr == NativeMethods.ERROR_HANDLE_EOF)
InternalResources.EndOfFile();
else
InternalResources.WinIOError(hr, String.Empty);
}
}
return asyncResult;
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
unsafe private SerialStreamAsyncResult BeginWriteCore(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)
{
// Create and store async stream class library specific data in the
// async result
SerialStreamAsyncResult asyncResult = new SerialStreamAsyncResult();
asyncResult._userCallback = userCallback;
asyncResult._userStateObject = stateObject;
asyncResult._isWrite = true;
// For Synchronous IO, I could go with either a callback and using
// the managed Monitor class, or I could create a handle and wait on it.
ManualResetEvent waitHandle = new ManualResetEvent(false);
asyncResult._waitHandle = waitHandle;
// Create a managed overlapped class
// We will set the file offsets later
Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, asyncResult);
// Pack the Overlapped class, and store it in the async result
NativeOverlapped* intOverlapped = overlapped.Pack(IOCallback, array);
asyncResult._overlapped = intOverlapped;
int hr = 0;
// queue an async WriteFile operation and pass in a packed overlapped
int r = WriteFileNative(array, offset, numBytes, intOverlapped, out hr);
// WriteFile, the OS version, will return 0 on failure. But
// my WriteFileNative wrapper returns -1. My wrapper will return
// the following:
// On error, r==-1.
// On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING
// On async requests that completed sequentially, r==0
// Note that you will NEVER RELIABLY be able to get the number of bytes
// written back from this call when using overlapped IO! You must
// not pass in a non-null lpNumBytesWritten to WriteFile when using
// overlapped structures!
if (r==-1)
{
if (hr != NativeMethods.ERROR_IO_PENDING)
{
if (hr == NativeMethods.ERROR_HANDLE_EOF)
InternalResources.EndOfFile();
else
InternalResources.WinIOError(hr, String.Empty);
}
}
return asyncResult;
}
// Internal method, wrapping the PInvoke to ReadFile().
private unsafe int ReadFileNative(byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr)
{
// Don't corrupt memory when multiple threads are erroneously writing
// to this stream simultaneously.
if (bytes.Length - offset < count)
throw new IndexOutOfRangeException(SR.GetString(SR.IndexOutOfRange_IORaceCondition));
// You can't use the fixed statement on an array of length 0.
if (bytes.Length==0)
{
hr = 0;
return 0;
}
int r = 0;
int numBytesRead = 0;
fixed(byte* p = bytes)
{
if (isAsync)
r = UnsafeNativeMethods.ReadFile(_handle, p + offset, count, IntPtr.Zero, overlapped);
else
r = UnsafeNativeMethods.ReadFile(_handle, p + offset, count, out numBytesRead, IntPtr.Zero);
}
if (r==0)
{
hr = Marshal.GetLastWin32Error();
// Note: we should never silently ignore an error here without some
// extra work. We must make sure that BeginReadCore won't return an
// IAsyncResult that will cause EndRead to block, since the OS won't
// call AsyncFSCallback for us.
// For invalid handles, detect the error and mark our handle
// as closed to give slightly better error messages. Also
// help ensure we avoid handle recycling bugs.
if (hr == NativeMethods.ERROR_INVALID_HANDLE)
_handle.SetHandleAsInvalid();
return -1;
}
else
hr = 0;
return numBytesRead;
}
private unsafe int WriteFileNative(byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr)
{
// Don't corrupt memory when multiple threads are erroneously writing
// to this stream simultaneously. (Note that the OS is reading from
// the array we pass to WriteFile, but if we read beyond the end and
// that memory isn't allocated, we could get an AV.)
if (bytes.Length - offset < count)
throw new IndexOutOfRangeException(SR.GetString(SR.IndexOutOfRange_IORaceCondition));
// You can't use the fixed statement on an array of length 0.
if (bytes.Length==0)
{
hr = 0;
return 0;
}
int numBytesWritten = 0;
int r = 0;
fixed(byte* p = bytes)
{
if (isAsync)
r = UnsafeNativeMethods.WriteFile(_handle, p + offset, count, IntPtr.Zero, overlapped);
else
r = UnsafeNativeMethods.WriteFile(_handle, p + offset, count, out numBytesWritten, IntPtr.Zero);
}
if (r==0)
{
hr = Marshal.GetLastWin32Error();
// Note: we should never silently ignore an error here without some
// extra work. We must make sure that BeginWriteCore won't return an
// IAsyncResult that will cause EndWrite to block, since the OS won't
// call AsyncFSCallback for us.
// For invalid handles, detect the error and mark our handle
// as closed to give slightly better error messages. Also
// help ensure we avoid handle recycling bugs.
if (hr == NativeMethods.ERROR_INVALID_HANDLE)
_handle.SetHandleAsInvalid();
return -1;
}
else
hr = 0;
return numBytesWritten;
}
// ----SUBSECTION: internal methods supporting events/async operation------*
// This is a the callback prompted when a thread completes any async I/O operation.
unsafe private static void AsyncFSCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped)
{
// Unpack overlapped
Overlapped overlapped = Overlapped.Unpack(pOverlapped);
// Extract async the result from overlapped structure
SerialStreamAsyncResult asyncResult =
(SerialStreamAsyncResult)overlapped.AsyncResult;
asyncResult._numBytes = (int)numBytes;
asyncResult._errorCode = (int)errorCode;
// Call the user-provided callback. Note that it can and often should
// call EndRead or EndWrite. There's no reason to use an async
// delegate here - we're already on a threadpool thread.
// Note the IAsyncResult's completedSynchronously property must return
// false here, saying the user callback was called on another thread.
asyncResult._completedSynchronously = false;
asyncResult._isComplete = true;
// The OS does not signal this event. We must do it ourselves.
// But don't close it if the user callback called EndXxx,
// which then closed the manual reset event already.
ManualResetEvent wh = asyncResult._waitHandle;
if (wh != null) {
bool r = wh.Set();
if (!r) InternalResources.WinIOError();
}
AsyncCallback userCallback = asyncResult._userCallback;
if (userCallback != null)
userCallback(asyncResult);
}
// ----SECTION: internal classes --------*
internal sealed partial class EventLoopRunner {
private WeakReference streamWeakReference;
internal ManualResetEvent eventLoopEndedSignal = new ManualResetEvent(false);
internal ManualResetEvent waitCommEventWaitHandle = new ManualResetEvent(false);
private SafeFileHandle handle = null;
private bool isAsync;
internal bool endEventLoop;
private int eventsOccurred;
WaitCallback callErrorEvents;
WaitCallback callReceiveEvents;
WaitCallback callPinEvents;
IOCompletionCallback freeNativeOverlappedCallback;
#if DEBUG
private readonly string portName;
#endif
internal unsafe EventLoopRunner(SerialStream stream) {
handle = stream._handle;
streamWeakReference = new WeakReference(stream);
callErrorEvents = new WaitCallback(CallErrorEvents);
callReceiveEvents = new WaitCallback(CallReceiveEvents );
callPinEvents = new WaitCallback(CallPinEvents);
freeNativeOverlappedCallback = new IOCompletionCallback(FreeNativeOverlappedCallback);
isAsync = stream.isAsync;
#if DEBUG
portName = stream.portName;
#endif
}
internal bool ShutdownLoop {
get {
return endEventLoop;
}
}
// This is the blocking method that waits for an event to occur. It wraps the SDK's WaitCommEvent function.
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
[SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke", Justification = "this is debug-only code")]
internal unsafe void WaitForCommEvent()
{
int unused = 0;
bool doCleanup = false;
NativeOverlapped* intOverlapped = null;
while (!ShutdownLoop) {
SerialStreamAsyncResult asyncResult = null;
if (isAsync) {
asyncResult = new SerialStreamAsyncResult();
asyncResult._userCallback = null;
asyncResult._userStateObject = null;
asyncResult._isWrite = false;
// we're going to use _numBytes for something different in this loop. In this case, both
// freeNativeOverlappedCallback and this thread will decrement that value. Whichever one decrements it
// to zero will be the one to free the native overlapped. This guarantees the overlapped gets freed
// after both the callback and GetOverlappedResult have had a chance to use it.
asyncResult._numBytes = 2;
asyncResult._waitHandle = waitCommEventWaitHandle;
waitCommEventWaitHandle.Reset();
Overlapped overlapped = new Overlapped(0, 0, waitCommEventWaitHandle.SafeWaitHandle.DangerousGetHandle(), asyncResult);
// Pack the Overlapped class, and store it in the async result
intOverlapped = overlapped.Pack(freeNativeOverlappedCallback, null);
}
fixed (int* eventsOccurredPtr = &eventsOccurred) {
if (UnsafeNativeMethods.WaitCommEvent(handle, eventsOccurredPtr, intOverlapped) == false)
{
int hr = Marshal.GetLastWin32Error();
// When a device is disconnected unexpectedly from a serial port, there appear to be
// at least three error codes Windows or drivers may return.
const int ERROR_DEVICE_REMOVED = 1617;
if (hr == NativeMethods.ERROR_ACCESS_DENIED || hr == NativeMethods.ERROR_BAD_COMMAND || hr == ERROR_DEVICE_REMOVED) {
doCleanup = true;
break;
}
if (hr == NativeMethods.ERROR_IO_PENDING)
{
Debug.Assert(isAsync, "The port is not open for async, so we should not get ERROR_IO_PENDING from WaitCommEvent");
int error;
// if we get IO pending, MSDN says we should wait on the WaitHandle, then call GetOverlappedResult
// to get the results of WaitCommEvent.
bool success = waitCommEventWaitHandle.WaitOne();
Debug.Assert(success, "waitCommEventWaitHandle.WaitOne() returned error " + Marshal.GetLastWin32Error());
do {
// NOTE: GetOverlappedResult will modify the original pointer passed into WaitCommEvent.
success = UnsafeNativeMethods.GetOverlappedResult(handle, intOverlapped, ref unused, false);
error = Marshal.GetLastWin32Error();
}
while (error == NativeMethods.ERROR_IO_INCOMPLETE && !ShutdownLoop && !success);
if (!success) {
// Ignore ERROR_IO_INCOMPLETE and ERROR_INVALID_PARAMETER, because there's a chance we'll get
// one of those while shutting down
if (! ( (error == NativeMethods.ERROR_IO_INCOMPLETE || error == NativeMethods.ERROR_INVALID_PARAMETER) && ShutdownLoop))
Debug.Assert(false, "GetOverlappedResult returned error, we might leak intOverlapped memory" + error.ToString(CultureInfo.InvariantCulture));
}
}
else if (hr != NativeMethods.ERROR_INVALID_PARAMETER) {
// ignore ERROR_INVALID_PARAMETER errors. WaitCommError seems to return this
// when SetCommMask is changed while it's blocking (like we do in Dispose())
Debug.Assert(false, "WaitCommEvent returned error " + hr);
}
}
}
if (!ShutdownLoop)
CallEvents(eventsOccurred);
if (isAsync) {
if (Interlocked.Decrement(ref asyncResult._numBytes) == 0)
Overlapped.Free(intOverlapped);
}
} // while (!ShutdownLoop)
if (doCleanup) {
// the rest will be handled in Dispose()
endEventLoop = true;
Overlapped.Free(intOverlapped);
}
eventLoopEndedSignal.Set();
}
private unsafe void FreeNativeOverlappedCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) {
// Unpack overlapped
Overlapped overlapped = Overlapped.Unpack(pOverlapped);
// Extract the async result from overlapped structure
SerialStreamAsyncResult asyncResult =
(SerialStreamAsyncResult)overlapped.AsyncResult;
if (Interlocked.Decrement(ref asyncResult._numBytes) == 0)
Overlapped.Free(pOverlapped);
}
private void CallEvents(int nativeEvents)
{
// EV_ERR includes only CE_FRAME, CE_OVERRUN, and CE_RXPARITY
// To catch errors such as CE_RXOVER, we need to call CleanCommErrors bit more regularly.
// EV_RXCHAR is perhaps too loose an event to look for overflow errors but a safe side to err...
if ((nativeEvents & (NativeMethods.EV_ERR | NativeMethods.EV_RXCHAR)) != 0) {
int errors = 0;
if (UnsafeNativeMethods.ClearCommError(handle, ref errors, IntPtr.Zero) == false) {
//InternalResources.WinIOError();
// We don't want to throw an exception from the background thread which is un-catchable and hence tear down the process.
// At present we don't have a first class event that we can raise for this class of fatal errors. One possibility is
// to overload SeralErrors event to include another enum (perhaps CE_IOE) that we can use for this purpose.
// In the absene of that, it is better to eat this error silently than tearing down the process (lesser of the evil).
// This uncleared comm error will most likely ---- up when the device is accessed by other APIs (such as Read) on the
// main thread and hence become known. It is bit roundabout but acceptable.
//
// Shutdown the event runner loop (probably bit drastic but we did come across a fatal error).
// Defer actual dispose chores until finalization though.
endEventLoop = true;
Thread.MemoryBarrier();
return;
}
errors = errors & errorEvents;
//
if (errors != 0) {
ThreadPool.QueueUserWorkItem(callErrorEvents, errors);
}
}
// now look for pin changed and received events.
if ((nativeEvents & pinChangedEvents) != 0) {
ThreadPool.QueueUserWorkItem(callPinEvents, nativeEvents);
}
if ((nativeEvents & receivedEvents) != 0) {
ThreadPool.QueueUserWorkItem(callReceiveEvents, nativeEvents);
}
}
private void CallErrorEvents(object state) {
int errors = (int) state;
SerialStream stream = (SerialStream) streamWeakReference.Target;
if (stream == null)
return;
if (stream.ErrorReceived != null) {
if ((errors & (int) SerialError.TXFull) != 0)
stream.ErrorReceived(stream, new SerialErrorReceivedEventArgs(SerialError.TXFull));
if ((errors & (int) SerialError.RXOver) != 0)
stream.ErrorReceived(stream, new SerialErrorReceivedEventArgs(SerialError.RXOver));
if ((errors & (int) SerialError.Overrun) != 0)
stream.ErrorReceived(stream, new SerialErrorReceivedEventArgs(SerialError.Overrun));
if ((errors & (int) SerialError.RXParity) != 0)
stream.ErrorReceived(stream, new SerialErrorReceivedEventArgs(SerialError.RXParity));
if ((errors & (int) SerialError.Frame) != 0)
stream.ErrorReceived(stream, new SerialErrorReceivedEventArgs(SerialError.Frame));
}
stream = null;
}
private void CallReceiveEvents(object state) {
int nativeEvents = (int) state;
SerialStream stream = (SerialStream) streamWeakReference.Target;
if (stream == null)
return;
if (stream.DataReceived != null) {
if ((nativeEvents & (int) SerialData.Chars) != 0)
stream.DataReceived(stream, new SerialDataReceivedEventArgs(SerialData.Chars));
if ((nativeEvents & (int) SerialData.Eof) != 0)
stream.DataReceived(stream, new SerialDataReceivedEventArgs(SerialData.Eof));
}
stream = null;
}
private void CallPinEvents(object state) {
int nativeEvents = (int) state;
SerialStream stream = (SerialStream) streamWeakReference.Target;
if (stream == null)
return;
if (stream.PinChanged != null) {
if ((nativeEvents & (int) SerialPinChange.CtsChanged) != 0)
stream.PinChanged(stream, new SerialPinChangedEventArgs(SerialPinChange.CtsChanged));
if ((nativeEvents & (int) SerialPinChange.DsrChanged) != 0)
stream.PinChanged(stream, new SerialPinChangedEventArgs(SerialPinChange.DsrChanged));
if ((nativeEvents & (int) SerialPinChange.CDChanged) != 0)
stream.PinChanged(stream, new SerialPinChangedEventArgs(SerialPinChange.CDChanged));
if ((nativeEvents & (int) SerialPinChange.Ring) != 0)
stream.PinChanged(stream, new SerialPinChangedEventArgs(SerialPinChange.Ring));
if ((nativeEvents & (int) SerialPinChange.Break) != 0)
stream.PinChanged(stream, new SerialPinChangedEventArgs(SerialPinChange.Break));
}
stream = null;
}
}
// This is an internal object implementing IAsyncResult with fields
// for all of the relevant data necessary to complete the IO operation.
// This is used by AsyncFSCallback and all async methods.
unsafe internal sealed class SerialStreamAsyncResult : IAsyncResult
{
// User code callback
internal AsyncCallback _userCallback;
internal Object _userStateObject;
internal bool _isWrite; // Whether this is a read or a write
internal bool _isComplete;
internal bool _completedSynchronously; // Which thread called callback
internal ManualResetEvent _waitHandle;
internal int _EndXxxCalled; // Whether we've called EndXxx already.
internal int _numBytes; // number of bytes read OR written
internal int _errorCode;
internal NativeOverlapped* _overlapped;
public Object AsyncState
{
get { return _userStateObject; }
}
public bool IsCompleted
{
get { return _isComplete; }
}
public WaitHandle AsyncWaitHandle
{
get {
/*
// Consider uncommenting this someday soon - the EventHandle
// in the Overlapped struct is really useless half of the
// time today since the OS doesn't signal it. If users call
// EndXxx after the OS call happened to complete, there's no
// reason to create a synchronization primitive here. Fixing
// this will save us some perf, assuming we can correctly
// initialize the ManualResetEvent.
if (_waitHandle == null) {
ManualResetEvent mre = new ManualResetEvent(false);
if (_overlapped != null && _overlapped->EventHandle != IntPtr.Zero)
mre.Handle = _overlapped->EventHandle;
if (_isComplete)
mre.Set();
_waitHandle = mre;
}
*/
return _waitHandle;
}
}
// Returns true iff the user callback was called by the thread that
// called BeginRead or BeginWrite. If we use an async delegate or
// threadpool thread internally, this will be false. This is used
// by code to determine whether a successive call to BeginRead needs
// to be done on their main thread or in their callback to avoid a
// stack overflow on many reads or writes.
public bool CompletedSynchronously
{
get { return _completedSynchronously; }
}
}
}
}
|