1990 lines
88 KiB
C#
1990 lines
88 KiB
C#
// ==++==
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ==--==
|
|
/*=============================================================================
|
|
**
|
|
** Class: SerialStream
|
|
**
|
|
** Purpose: Class for enabling low-level [....] 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 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 = new Thread(new ThreadStart(eventRunner.WaitForCommEvent));
|
|
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.
|
|
if ((hr == NativeMethods.ERROR_ACCESS_DENIED || hr == NativeMethods.ERROR_BAD_COMMAND) && !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 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 two error codes Windows or drivers may return.
|
|
if (hr == NativeMethods.ERROR_ACCESS_DENIED || hr == NativeMethods.ERROR_BAD_COMMAND) {
|
|
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; }
|
|
}
|
|
}
|
|
}
|
|
}
|