3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 /*============================================================
10 ** Purpose: SerialPort wraps an internal SerialStream class,
11 ** : providing a high but complete level of Serial Port I/O functionality
12 ** : over the handle/Win32 object level of the SerialStream.
17 ===========================================================*/
21 using System.ComponentModel;
22 using System.Collections;
23 using System.Diagnostics;
26 using System.Security;
27 using System.Security.Permissions;
28 using Microsoft.Win32;
29 using System.Runtime.InteropServices;
30 using System.Runtime.Versioning;
33 namespace System.IO.Ports
36 [MonitoringDescription(SR.SerialPortDesc)]
37 public class SerialPort : System.ComponentModel.Component
39 public const int InfiniteTimeout = -1;
41 // ---------- default values -------------*
43 private const int defaultDataBits = 8;
44 private const Parity defaultParity = Parity.None;
45 private const StopBits defaultStopBits = StopBits.One;
46 private const Handshake defaultHandshake = Handshake.None;
47 private const int defaultBufferSize = 1024;
48 private const string defaultPortName = "COM1";
49 private const int defaultBaudRate = 9600;
50 private const bool defaultDtrEnable = false;
51 private const bool defaultRtsEnable = false;
52 private const bool defaultDiscardNull = false;
53 private const byte defaultParityReplace = (byte) '?';
54 private const int defaultReceivedBytesThreshold = 1;
55 private const int defaultReadTimeout = SerialPort.InfiniteTimeout;
56 private const int defaultWriteTimeout = SerialPort.InfiniteTimeout;
57 private const int defaultReadBufferSize = 4096;
58 private const int defaultWriteBufferSize = 2048;
59 private const int maxDataBits = 8;
60 private const int minDataBits = 5;
61 private const string defaultNewLine = "\n";
63 private const string SERIAL_NAME = @"\Device\Serial";
65 // --------- members supporting exposed properties ------------*
66 private int baudRate = defaultBaudRate;
67 private int dataBits = defaultDataBits;
68 private Parity parity = defaultParity;
69 private StopBits stopBits = defaultStopBits;
70 private string portName = defaultPortName;
71 private Encoding encoding = System.Text.Encoding.ASCII; // ASCII is default encoding for modem communication, etc.
72 private Decoder decoder = System.Text.Encoding.ASCII.GetDecoder();
73 private int maxByteCountForSingleChar = System.Text.Encoding.ASCII.GetMaxByteCount(1);
74 private Handshake handshake = defaultHandshake;
75 private int readTimeout = defaultReadTimeout;
76 private int writeTimeout = defaultWriteTimeout;
77 private int receivedBytesThreshold = defaultReceivedBytesThreshold;
78 private bool discardNull = defaultDiscardNull;
79 private bool dtrEnable = defaultDtrEnable;
80 private bool rtsEnable = defaultRtsEnable;
81 private byte parityReplace = defaultParityReplace;
82 private string newLine = defaultNewLine;
83 private int readBufferSize = defaultReadBufferSize;
84 private int writeBufferSize = defaultWriteBufferSize;
86 // ---------- members for internal support ---------*
87 private SerialStream internalSerialStream = null;
88 private byte[] inBuffer = new byte[defaultBufferSize];
89 private int readPos = 0; // position of next byte to read in the read buffer. readPos <= readLen
90 private int readLen = 0; // position of first unreadable byte => CachedBytesToRead is the number of readable bytes left.
91 private char[] oneChar = new char[1];
92 private char[] singleCharBuffer = null;
94 // ------ event members ------------------*
95 //public event EventHandler Disposed;
96 [MonitoringDescription(SR.SerialErrorReceived)]
97 public event SerialErrorReceivedEventHandler ErrorReceived;
99 [MonitoringDescription(SR.SerialPinChanged)]
100 public event SerialPinChangedEventHandler PinChanged;
102 [MonitoringDescription(SR.SerialDataReceived)]
103 public event SerialDataReceivedEventHandler DataReceived;
105 //--- component properties---------------*
107 // ---- SECTION: public properties --------------*
108 // Note: information about port properties passes in ONE direction: from SerialPort to
109 // its underlying Stream. No changes are able to be made in the important properties of
110 // the stream and its behavior, so no reflection back to SerialPort is necessary.
112 // Gets the internal SerialStream object. Used to pass essence of SerialPort to another Stream wrapper.
114 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
115 public Stream BaseStream
119 throw new InvalidOperationException(SR.GetString(SR.BaseStream_Invalid_Not_Open));
121 return internalSerialStream;
126 DefaultValue(defaultBaudRate),
127 MonitoringDescription(SR.BaudRate)]
130 get { return baudRate; }
133 throw new ArgumentOutOfRangeException("BaudRate", SR.GetString(SR.ArgumentOutOfRange_NeedPosNum));
136 internalSerialStream.BaudRate = value;
142 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
143 public bool BreakState
148 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
150 return internalSerialStream.BreakState;
155 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
157 internalSerialStream.BreakState = value;
161 // includes all bytes available on serial driver's output buffer. Note that we do not internally buffer output bytes in SerialPort.
163 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
164 public int BytesToWrite
169 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
170 return internalSerialStream.BytesToWrite;
174 // includes all bytes available on serial driver's input buffer as well as bytes internally buffered int the SerialPort class.
176 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
177 public int BytesToRead
182 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
183 return internalSerialStream.BytesToRead + CachedBytesToRead; // count the number of bytes we have in the internal buffer too.
187 private int CachedBytesToRead {
189 return readLen - readPos;
194 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
195 public bool CDHolding
200 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
201 return internalSerialStream.CDHolding;
206 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
207 public bool CtsHolding
212 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
213 return internalSerialStream.CtsHolding;
219 DefaultValue(defaultDataBits),
220 MonitoringDescription(SR.DataBits)]
227 if (value < minDataBits || value > maxDataBits)
228 throw new ArgumentOutOfRangeException("DataBits", SR.GetString(SR.ArgumentOutOfRange_Bounds_Lower_Upper, minDataBits, maxDataBits));
231 internalSerialStream.DataBits = value;
237 DefaultValue(defaultDiscardNull),
238 MonitoringDescription(SR.DiscardNull)]
239 public bool DiscardNull
248 internalSerialStream.DiscardNull = value;
254 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
255 public bool DsrHolding
260 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
261 return internalSerialStream.DsrHolding;
266 DefaultValue(defaultDtrEnable),
267 MonitoringDescription(SR.DtrEnable)]
268 public bool DtrEnable
272 dtrEnable = internalSerialStream.DtrEnable;
279 internalSerialStream.DtrEnable = value;
284 // Allows specification of an arbitrary encoding for the reading and writing functions of the port
285 // which deal with chars and strings. Set by default in the code to System.Text.ASCIIEncoding(), which
286 // is the standard text encoding for modem commands and most of serial communication.
287 // Clearly not designable.
289 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
290 MonitoringDescription(SR.EncodingMonitoringDescription)]
291 public Encoding Encoding
298 throw new ArgumentNullException("Encoding");
300 // Limit the encodings we support to some known ones. The code pages < 50000 represent all of the single-byte
301 // and double-byte code pages. Code page 54936 is GB18030. Finally we check that the encoding's assembly
302 // is mscorlib, so we don't get any weird user encodings that happen to set a code page less than 50000.
303 if (!(value is ASCIIEncoding || value is UTF8Encoding || value is UnicodeEncoding || value is UTF32Encoding ||
304 ((value.CodePage < 50000 || value.CodePage == 54936)&& value.GetType().Assembly == typeof(String).Assembly))) {
306 throw new ArgumentException(SR.GetString(SR.NotSupportedEncoding, value.WebName), "value");
310 decoder = encoding.GetDecoder();
312 // This is somewhat of an approximate guesstimate to get the max char[] size needed to encode a single character
313 maxByteCountForSingleChar = encoding.GetMaxByteCount(1);
314 singleCharBuffer = null;
319 DefaultValue(defaultHandshake),
320 MonitoringDescription(SR.Handshake)]
321 public Handshake Handshake
329 if (value < Handshake.None || value > Handshake.RequestToSendXOnXOff)
330 throw new ArgumentOutOfRangeException("Handshake", SR.GetString(SR.ArgumentOutOfRange_Enum));
333 internalSerialStream.Handshake = value;
338 // true only if the Open() method successfully called on this SerialPort object, without Close() being called more recently.
342 get { return (internalSerialStream != null && internalSerialStream.IsOpen); }
347 DefaultValue(defaultNewLine),
348 MonitoringDescription(SR.NewLine)
350 public string NewLine {
351 get { return newLine; }
354 throw new ArgumentNullException();
355 if (value.Length == 0)
356 throw new ArgumentException(SR.GetString(SR.InvalidNullEmptyArgument, "NewLine"));
363 DefaultValue(defaultParity),
364 MonitoringDescription(SR.Parity)]
374 if (value < Parity.None || value > Parity.Space)
375 throw new ArgumentOutOfRangeException("Parity", SR.GetString(SR.ArgumentOutOfRange_Enum));
378 internalSerialStream.Parity = value;
384 DefaultValue(defaultParityReplace),
385 MonitoringDescription(SR.ParityReplace)]
386 public byte ParityReplace
388 get { return parityReplace; }
392 internalSerialStream.ParityReplace = value;
393 parityReplace = value;
399 // Note that the communications port cannot be meaningfully re-set when the port is open,
400 // and so once set by the constructor becomes read-only.
402 DefaultValue(defaultPortName),
403 MonitoringDescription(SR.PortName)]
404 public string PortName
409 [ResourceExposure(ResourceScope.Machine)]
413 throw new ArgumentNullException("PortName");
414 if (value.Length ==0)
415 throw new ArgumentException(SR.GetString(SR.PortNameEmpty_String), "PortName");
417 // disallow access to device resources beginning with @"\\", instead requiring "COM2:", etc.
418 // Note that this still allows freedom in mapping names to ports, etc., but blocks security leaks.
419 if (value.StartsWith("\\\\", StringComparison.Ordinal))
420 throw new ArgumentException(SR.GetString(SR.Arg_SecurityException), "PortName");
423 throw new InvalidOperationException(SR.GetString(SR.Cant_be_set_when_open, "PortName"));
429 DefaultValue(defaultReadBufferSize),
430 MonitoringDescription(SR.ReadBufferSize)]
431 public int ReadBufferSize {
433 return readBufferSize;
437 throw new ArgumentOutOfRangeException("value");
440 throw new InvalidOperationException(SR.GetString(SR.Cant_be_set_when_open, "value"));
442 readBufferSize = value;
446 // timeout for all read operations. May be set to SerialPort.InfiniteTimeout, 0, or any positive value
448 DefaultValue(SerialPort.InfiniteTimeout),
449 MonitoringDescription(SR.ReadTimeout)]
450 public int ReadTimeout
459 if (value < 0 && value != SerialPort.InfiniteTimeout)
460 throw new ArgumentOutOfRangeException("ReadTimeout", SR.GetString(SR.ArgumentOutOfRange_Timeout));
463 internalSerialStream.ReadTimeout = value;
469 DefaultValue(defaultReceivedBytesThreshold),
470 MonitoringDescription(SR.ReceivedBytesThreshold)]
471 // If we have the SerialData.Chars event set, this property indicates the number of bytes necessary
472 // to exist in our buffers before the event is thrown. This is useful if we expect to receive n-byte
473 // packets and can only act when we have this many, etc.
474 public int ReceivedBytesThreshold
478 return receivedBytesThreshold;
484 throw new ArgumentOutOfRangeException("ReceivedBytesThreshold",
485 SR.GetString(SR.ArgumentOutOfRange_NeedPosNum));
486 receivedBytesThreshold = value;
489 // fake the call to our event handler in case the threshold has been set lower
490 // than how many bytes we currently have.
491 SerialDataReceivedEventArgs args = new SerialDataReceivedEventArgs(SerialData.Chars);
492 CatchReceivedEvents(this, args);
498 DefaultValue(defaultRtsEnable),
499 MonitoringDescription(SR.RtsEnable)]
500 public bool RtsEnable
505 rtsEnable = internalSerialStream.RtsEnable;
512 internalSerialStream.RtsEnable = value;
517 // StopBits represented in C# as StopBits enum type and in Win32 as an integer 1, 2, or 3.
519 DefaultValue(defaultStopBits),
520 MonitoringDescription(SR.StopBits)
522 public StopBits StopBits
530 // this range check looks wrong, but it really is correct. One = 1, Two = 2, and OnePointFive = 3
531 if (value < StopBits.One || value > StopBits.OnePointFive)
532 throw new ArgumentOutOfRangeException("StopBits", SR.GetString(SR.ArgumentOutOfRange_Enum));
535 internalSerialStream.StopBits = value;
541 DefaultValue(defaultWriteBufferSize),
542 MonitoringDescription(SR.WriteBufferSize)]
543 public int WriteBufferSize {
545 return writeBufferSize;
549 throw new ArgumentOutOfRangeException("value");
552 throw new InvalidOperationException(SR.GetString(SR.Cant_be_set_when_open, "value"));
554 writeBufferSize = value;
558 // timeout for all write operations. May be set to SerialPort.InfiniteTimeout or any positive value
560 DefaultValue(defaultWriteTimeout),
561 MonitoringDescription(SR.WriteTimeout)]
562 public int WriteTimeout
570 if (value <= 0 && value != SerialPort.InfiniteTimeout)
571 throw new ArgumentOutOfRangeException("WriteTimeout", SR.GetString(SR.ArgumentOutOfRange_WriteTimeout));
574 internalSerialStream.WriteTimeout = value;
575 writeTimeout = value;
581 // -------- SECTION: constructors -----------------*
582 public SerialPort(System.ComponentModel.IContainer container)
585 /// Required for Windows.Forms Class Composition Designer support
594 // Non-design SerialPort constructors here chain, using default values for members left unspecified by parameters
595 // Note: Calling SerialPort() does not open a port connection but merely instantiates an object.
596 // : A connection must be made using SerialPort's Open() method.
597 [ResourceExposure(ResourceScope.Machine)]
598 [ResourceConsumption(ResourceScope.Machine)]
599 public SerialPort(string portName) : this (portName, defaultBaudRate, defaultParity, defaultDataBits, defaultStopBits)
603 [ResourceExposure(ResourceScope.Machine)]
604 [ResourceConsumption(ResourceScope.Machine)]
605 public SerialPort(string portName, int baudRate) : this (portName, baudRate, defaultParity, defaultDataBits, defaultStopBits)
609 [ResourceExposure(ResourceScope.Machine)]
610 [ResourceConsumption(ResourceScope.Machine)]
611 public SerialPort(string portName, int baudRate, Parity parity) : this (portName, baudRate, parity, defaultDataBits, defaultStopBits)
615 [ResourceExposure(ResourceScope.Machine)]
616 [ResourceConsumption(ResourceScope.Machine)]
617 public SerialPort(string portName, int baudRate, Parity parity, int dataBits) : this (portName, baudRate, parity, dataBits, defaultStopBits)
621 // all the magic happens in the call to the instance's .Open() method.
622 // Internally, the SerialStream constructor opens the file handle, sets the device
623 // control block and associated Win32 structures, and begins the event-watching cycle.
624 [ResourceExposure(ResourceScope.Machine)]
625 [ResourceConsumption(ResourceScope.Machine)]
626 public SerialPort(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
628 this.PortName = portName;
629 this.BaudRate = baudRate;
630 this.Parity = parity;
631 this.DataBits = dataBits;
632 this.StopBits = stopBits;
635 // Calls internal Serial Stream's Close() method on the internal Serial Stream.
641 protected override void Dispose( bool disposing )
645 internalSerialStream.Flush();
646 internalSerialStream.Close();
647 internalSerialStream = null;
650 base.Dispose( disposing );
653 public void DiscardInBuffer()
656 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
657 internalSerialStream.DiscardInBuffer();
658 readPos = readLen = 0;
661 public void DiscardOutBuffer()
664 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
665 internalSerialStream.DiscardOutBuffer();
668 [ResourceExposure(ResourceScope.Machine)]
669 [ResourceConsumption(ResourceScope.Machine)]
670 public static string[] GetPortNames() {
671 RegistryKey baseKey = null;
672 RegistryKey serialKey = null;
674 String[] portNames = null;
676 RegistryPermission registryPermission = new RegistryPermission(RegistryPermissionAccess.Read,
677 @"HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM");
678 registryPermission.Assert();
681 baseKey = Registry.LocalMachine;
682 serialKey = baseKey.OpenSubKey(@"HARDWARE\DEVICEMAP\SERIALCOMM", false);
684 if (serialKey != null) {
686 string[] deviceNames = serialKey.GetValueNames();
687 portNames = new String[deviceNames.Length];
689 for (int i=0; i<deviceNames.Length; i++)
690 portNames[i] = (string)serialKey.GetValue(deviceNames[i]);
697 if (serialKey != null)
700 RegistryPermission.RevertAssert();
703 // If serialKey didn't exist for some reason
704 if (portNames == null)
705 portNames = new String[0];
711 public static string[] GetPortNames() {
712 if (Environment.OSVersion.Platform == PlatformID.Win32Windows)
713 throw new PlatformNotSupportedException(SR.GetString(SR.NotSupportedOS));
715 // Get all the registered serial device names
716 RegistryPermission registryPermission = new RegistryPermission(PermissionState.Unrestricted);
717 registryPermission.Assert();
719 RegistryKey baseKey = null;
720 RegistryKey serialKey = null;
722 Hashtable portNames = new Hashtable(10);
725 baseKey = Registry.LocalMachine;
726 serialKey = baseKey.OpenSubKey(@"HARDWARE\DEVICEMAP\SERIALCOMM", true);
728 if (serialKey != null) {
730 string[] devices = serialKey.GetValueNames();
731 for (int j=0; j<devices.Length; j++) {
732 portNames.Add(devices[j], null);
740 if (serialKey != null)
743 RegistryPermission.RevertAssert();
746 // Get all the MS-DOS names on the local machine
747 //(sending null for lpctstrName gets all the names)
749 char[] buffer = CallQueryDosDevice(null, out dataSize);
751 // From QueryDosDevice, we get back a long string where the names are delimited by \0 and the end
752 // of the string is indicated by two \0s
753 ArrayList names = new ArrayList();
754 ArrayList deviceNames = new ArrayList();
757 while (i < dataSize) {
758 // Walk through the buffer building a name until we hit the delimiter \0
760 while (buffer[i] != '\0') {
765 // We now have an MS-DOS name (the common name). We call QueryDosDevice again with
766 // this name to get the underlying system name mapped to the MS-DOS name.
767 string currentName = (new String(buffer, start, i-start)).Trim();
769 char[] nameBuffer = CallQueryDosDevice(currentName, out nameSize);
771 // If we got a system name, see if it's a serial port name. If it is, add the common name
774 // internalName will include the trailing null chars as well as any additional
775 // names that may get returned. This is ok, since we are only interested in the
776 // first name and we can use StartsWith.
777 string internalName = new string(nameBuffer, 0, nameSize-2).Trim();
779 if (internalName.StartsWith(SERIAL_NAME) || portNames.ContainsKey(internalName)) {
780 names.Add(currentName);
781 deviceNames.Add(internalName);
788 string[] namesArray = new String[names.Count];
789 names.CopyTo(namesArray);
791 string[] deviceNamesArray = new String[deviceNames.Count];
792 deviceNames.CopyTo(deviceNamesArray);
794 // sort the common names according to their actual device ordering
795 Array.Sort(deviceNamesArray, namesArray, Comparer.DefaultInvariant);
800 private static unsafe char[] CallQueryDosDevice(string name, out int dataSize) {
801 char[] buffer = new char[1024];
803 fixed (char *bufferPtr = buffer) {
804 dataSize = UnsafeNativeMethods.QueryDosDevice(name, buffer, buffer.Length);
805 while (dataSize <= 0) {
806 int lastError = Marshal.GetLastWin32Error();
807 if (lastError == NativeMethods.ERROR_INSUFFICIENT_BUFFER || lastError == NativeMethods.ERROR_MORE_DATA) {
808 buffer = new char[buffer.Length * 2];
809 dataSize = UnsafeNativeMethods.QueryDosDevice(null, buffer, buffer.Length);
812 throw new Win32Exception();
820 // SerialPort is open <=> SerialPort has an associated SerialStream.
821 // The two statements are functionally equivalent here, so this method basically calls underlying Stream's
822 // constructor from the main properties specified in SerialPort: baud, stopBits, parity, dataBits,
823 // comm portName, handshaking, and timeouts.
824 [ResourceExposure(ResourceScope.None)] // Look at Name property
825 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
829 throw new InvalidOperationException(SR.GetString(SR.Port_already_open));
831 // Demand unmanaged code permission
832 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
834 internalSerialStream = new SerialStream(portName, baudRate, parity, dataBits, stopBits, readTimeout,
835 writeTimeout, handshake, dtrEnable, rtsEnable, discardNull, parityReplace);
837 internalSerialStream.SetBufferSizes(readBufferSize, writeBufferSize);
839 internalSerialStream.ErrorReceived += new SerialErrorReceivedEventHandler(CatchErrorEvents);
840 internalSerialStream.PinChanged += new SerialPinChangedEventHandler(CatchPinChangedEvents);
841 internalSerialStream.DataReceived += new SerialDataReceivedEventHandler(CatchReceivedEvents);
844 // Read Design pattern:
845 // : ReadChar() returns the first available full char if found before, throws TimeoutExc if timeout.
846 // : Read(byte[] buffer..., int count) returns all data available before read timeout expires up to *count* bytes
847 // : Read(char[] buffer..., int count) returns all data available before read timeout expires up to *count* chars.
848 // : Note, this does not return "half-characters".
849 // : ReadByte() is the binary analogue of the first one.
850 // : ReadLine(): returns null string on timeout, saves received data in buffer
851 // : ReadAvailable(): returns all full characters which are IMMEDIATELY available.
853 public int Read(byte[] buffer, int offset, int count)
856 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
858 throw new ArgumentNullException("buffer", SR.GetString(SR.ArgumentNull_Buffer));
860 throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
862 throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
863 if (buffer.Length - offset < count)
864 throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
865 int bytesReadToBuffer=0;
867 // if any bytes available in internal buffer, return those without calling any read ops.
868 if (CachedBytesToRead >= 1)
870 bytesReadToBuffer = Math.Min(CachedBytesToRead, count);
871 Buffer.BlockCopy(inBuffer, readPos, buffer, offset, bytesReadToBuffer);
872 readPos += bytesReadToBuffer;
873 if (bytesReadToBuffer == count) {
874 if (readPos == readLen) readPos = readLen = 0; // just a check to see if we can reset buffer
878 // if we have read some bytes but there's none immediately available, return.
879 if (BytesToRead == 0)
880 return bytesReadToBuffer;
883 Debug.Assert(CachedBytesToRead == 0, "there should be nothing left in our internal buffer");
884 readLen = readPos = 0;
886 int bytesLeftToRead = count - bytesReadToBuffer;
888 // request to read the requested number of bytes to fulfill the contract,
889 // doesn't matter if we time out. We still return all the data we have available.
890 bytesReadToBuffer += internalSerialStream.Read(buffer, offset + bytesReadToBuffer, bytesLeftToRead);
893 return bytesReadToBuffer;
896 // publicly exposed "ReadOneChar"-type: Read()
897 // reads one full character from the stream
898 public int ReadChar()
901 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
903 return ReadOneChar(readTimeout);
906 // gets next available full character, which may be from the buffer, the stream, or both.
907 // this takes size^2 time at most, where *size* is the maximum size of any one character in an encoding.
908 // The user can call Read(1) to mimic this functionality.
910 // We can replace ReadOneChar with Read at some point
911 private int ReadOneChar(int timeout)
915 Debug.Assert(IsOpen, "ReadOneChar - port not open");
917 // case 1: we have >= 1 character in the internal buffer.
918 if (decoder.GetCharCount(inBuffer, readPos, CachedBytesToRead) != 0)
920 int beginReadPos = readPos;
921 // get characters from buffer.
925 } while (decoder.GetCharCount(inBuffer, beginReadPos, readPos - beginReadPos) < 1);
928 decoder.GetChars(inBuffer, beginReadPos, readPos - beginReadPos, oneChar, 0);
932 // Handle surrogate chars correctly, restore readPos
933 readPos = beginReadPos;
941 // need to return immediately.
943 // read all bytes in the serial driver in here. Make sure we ask for at least 1 byte
944 // so that we get the proper timeout behavior
945 int bytesInStream = internalSerialStream.BytesToRead;
946 if (bytesInStream == 0)
948 MaybeResizeBuffer(bytesInStream);
949 readLen += internalSerialStream.Read(inBuffer, readLen, bytesInStream); // read all immediately avail.
951 // If what we have in the buffer is not enough, throw TimeoutExc
952 // if we are reading surrogate char then ReadBufferIntoChars
953 // will throw argexc and that is okay as readPos is not altered
954 if (ReadBufferIntoChars(oneChar, 0, 1, false) == 0)
955 throw new TimeoutException();
960 // case 2: we need to read from outside to find this.
961 // timeout is either infinite or positive.
962 int startTicks = Environment.TickCount;
965 if (timeout == SerialPort.InfiniteTimeout)
966 nextByte = internalSerialStream.ReadByte(InfiniteTimeout);
967 else if (timeout - timeUsed >= 0) {
968 nextByte = internalSerialStream.ReadByte(timeout - timeUsed);
969 timeUsed = Environment.TickCount - startTicks;
972 throw new TimeoutException();
974 MaybeResizeBuffer(1);
975 inBuffer[readLen++] = (byte) nextByte; // we must add to the end of the buffer
976 } while (decoder.GetCharCount(inBuffer, readPos, readLen - readPos) < 1);
979 // If we are reading surrogate char then this will throw argexc
980 // we need not deal with that exc because we have not altered readPos yet.
981 decoder.GetChars(inBuffer, readPos, readLen - readPos, oneChar, 0);
983 // Everything should be out of inBuffer now. We'll just reset the pointers.
984 readLen = readPos = 0;
988 // Will return 'n' (1 < n < count) characters (or) TimeoutExc
989 public int Read(char[] buffer, int offset, int count)
992 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
994 throw new ArgumentNullException("buffer", SR.GetString(SR.ArgumentNull_Buffer));
996 throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
998 throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
999 if (buffer.Length - offset < count)
1000 throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
1002 return InternalRead(buffer, offset, count, readTimeout, false);
1005 private int InternalRead(char[] buffer, int offset, int count, int timeout, bool countMultiByteCharsAsOne)
1007 Debug.Assert(IsOpen, "port not open!");
1008 Debug.Assert(buffer!=null, "invalid buffer!");
1009 Debug.Assert(offset >= 0, "invalid offset!");
1010 Debug.Assert(count >= 0, "invalid count!");
1011 Debug.Assert(buffer.Length - offset >= count, "invalid offset/count!");
1013 if (count == 0) return 0; // immediately return on zero chars desired. This simplifies things later.
1015 // Get the startticks before we read the underlying stream
1016 int startTicks = Environment.TickCount;
1018 // read everything else into internal buffer, which we know we can do instantly, and see if we NOW have enough.
1019 int bytesInStream = internalSerialStream.BytesToRead;
1020 MaybeResizeBuffer(bytesInStream);
1021 readLen += internalSerialStream.Read(inBuffer, readLen, bytesInStream); // should execute instantaneously.
1023 int charsWeAlreadyHave = decoder.GetCharCount(inBuffer, readPos, CachedBytesToRead); // full chars already in our buffer
1024 if (charsWeAlreadyHave > 0)
1026 // we found some chars after reading everything the SerialStream had to offer. We'll return what we have
1027 // rather than wait for more.
1028 return ReadBufferIntoChars(buffer, offset, count, countMultiByteCharsAsOne);
1032 throw new TimeoutException();
1034 // else: we need to do incremental reads from the stream.
1036 // our internal algorithm for finding exactly n characters is a bit complicated, but must overcome the
1037 // hurdle of NEVER READING TOO MANY BYTES from the Stream, since we can time out. A variable-length encoding
1038 // allows anywhere between minimum and maximum bytes per char times number of chars to be the exactly correct
1039 // target, and we have to take care not to overuse GetCharCount(). The problem is that GetCharCount() will never tell
1040 // us if we've read "half" a character in our current set of collected bytes; it underestimates.
1041 // size = maximum bytes per character in the encoding. n = number of characters requested.
1042 // Solution I: Use ReadOneChar() to read successive characters until we get to n.
1043 // Read calls: size * n; GetCharCount calls: size * n; each byte "counted": size times.
1044 // Solution II: Use a binary reduction and backtracking to reduce the number of calls.
1045 // Read calls: size * log n; GetCharCount calls: size * log n; each byte "counted": size * (log n) / n times.
1046 // We use the second, more complicated solution here. Note log is actually log_(size/size - 1)...
1049 // we need to read some from the stream
1050 // read *up to* the maximum number of bytes from the stream
1051 // we can read more since we receive everything instantaneously, and we don't have enough,
1052 // so when we do receive any data, it will be necessary and sufficient.
1055 int maxReadSize = Encoding.GetMaxByteCount(count);
1057 MaybeResizeBuffer(maxReadSize);
1059 readLen += internalSerialStream.Read(inBuffer, readLen, maxReadSize);
1060 justRead = ReadBufferIntoChars(buffer, offset, count, countMultiByteCharsAsOne);
1064 } while (timeout == SerialPort.InfiniteTimeout || (timeout - GetElapsedTime(Environment.TickCount, startTicks) > 0));
1066 // must've timed out w/o getting a character.
1067 throw new TimeoutException();
1070 // ReadBufferIntoChars reads from Serial Port's inBuffer up to *count* chars and
1071 // places them in *buffer* starting at *offset*.
1072 // This does not call any stream Reads, and so takes "no time".
1073 // If the buffer specified is insufficient to accommodate surrogate characters
1074 // the call to underlying Decoder.GetChars will throw argexc.
1075 private int ReadBufferIntoChars(char[] buffer, int offset, int count, bool countMultiByteCharsAsOne)
1077 Debug.Assert(count != 0, "Count should never be zero. We will probably see bugs further down if count is 0.");
1079 int bytesToRead = Math.Min(count, CachedBytesToRead);
1081 // There are lots of checks to determine if this really is a single byte encoding with no
1082 // funky fallbacks that would make it not single byte
1083 DecoderReplacementFallback fallback = encoding.DecoderFallback as DecoderReplacementFallback;
1084 if (encoding.IsSingleByte && encoding.GetMaxCharCount(bytesToRead) == bytesToRead &&
1085 fallback != null && fallback.MaxCharCount == 1)
1087 // kill ASCII/ANSI encoding easily.
1088 // read at least one and at most *count* characters
1089 decoder.GetChars(inBuffer, readPos, bytesToRead, buffer, offset);
1091 readPos += bytesToRead;
1092 if (readPos == readLen) readPos = readLen = 0;
1098 // We want to turn inBuffer into at most count chars. This algorithm basically works like this:
1099 // 1) Take the largest step possible that won't give us too many chars
1100 // 2) If we find some chars, walk backwards until we find exactly how many bytes
1101 // they occupy. lastFullCharPos points to the end of the full chars.
1102 // 3) if we don't have enough chars for the buffer, goto #1
1104 int totalBytesExamined = 0; // total number of Bytes in inBuffer we've looked at
1105 int totalCharsFound = 0; // total number of chars we've found in inBuffer, totalCharsFound <= totalBytesExamined
1106 int currentBytesToExamine; // the number of additional bytes to examine for characters
1107 int currentCharsFound; // the number of additional chars found after examining currentBytesToExamine extra bytes
1108 int lastFullCharPos = readPos; // first index AFTER last full char read, capped at ReadLen.
1111 currentBytesToExamine = Math.Min(count - totalCharsFound, readLen - readPos - totalBytesExamined);
1112 if (currentBytesToExamine <= 0)
1115 totalBytesExamined += currentBytesToExamine;
1116 // recalculate currentBytesToExamine so that it includes leftover bytes from the last iteration.
1117 currentBytesToExamine = readPos + totalBytesExamined - lastFullCharPos;
1119 // make sure we don't go beyond the end of the valid data that we have.
1120 Debug.Assert((lastFullCharPos + currentBytesToExamine) <= readLen, "We should never be attempting to read more bytes than we have");
1122 currentCharsFound = decoder.GetCharCount(inBuffer, lastFullCharPos, currentBytesToExamine);
1124 if (currentCharsFound > 0)
1126 if ((totalCharsFound + currentCharsFound) > count) {
1128 // Multibyte unicode sequence (possibly surrogate chars)
1129 // at the end of the buffer. We should not split the sequence,
1130 // instead return with less chars now and defer reading them
1132 if (!countMultiByteCharsAsOne)
1135 // If we are here it is from ReadTo which attempts to read one logical character
1136 // at a time. The supplied singleCharBuffer should be large enough to accommodate
1137 // this multi-byte char
1138 Debug.Assert((buffer.Length - offset - totalCharsFound) >= currentCharsFound, "internal buffer to read one full unicode char sequence is not sufficient!");
1141 // go backwards until we know we have a full set of currentCharsFound bytes with no extra lead-bytes.
1142 int foundCharsByteLength = currentBytesToExamine;
1145 foundCharsByteLength--;
1146 } while (decoder.GetCharCount(inBuffer, lastFullCharPos, foundCharsByteLength) == currentCharsFound);
1148 // Fill into destination buffer all the COMPLETE characters we've read.
1149 // If the buffer specified is insufficient to accommodate surrogate character
1150 // the call to underlying Decoder.GetChars will throw argexc. We need not
1151 // deal with this exc because we have not altered readPos yet.
1152 decoder.GetChars(inBuffer, lastFullCharPos, foundCharsByteLength + 1, buffer, offset + totalCharsFound);
1153 lastFullCharPos = lastFullCharPos + foundCharsByteLength + 1; // update the end position of last known char.
1156 totalCharsFound += currentCharsFound;
1157 } while ((totalCharsFound < count) && (totalBytesExamined < CachedBytesToRead));
1159 readPos = lastFullCharPos;
1161 if (readPos == readLen) readPos = readLen = 0;
1162 return totalCharsFound;
1166 public int ReadByte()
1169 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1170 if (readLen != readPos) // stuff left in buffer, so we can read from it
1171 return inBuffer[readPos++];
1174 return internalSerialStream.ReadByte(); // otherwise, ask the stream.
1177 public string ReadExisting()
1180 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1182 byte [] bytesReceived = new byte[BytesToRead];
1184 if (readPos < readLen)
1185 { // stuff in internal buffer
1186 Buffer.BlockCopy(inBuffer, readPos, bytesReceived, 0, CachedBytesToRead);
1188 internalSerialStream.Read(bytesReceived, CachedBytesToRead, bytesReceived.Length - (CachedBytesToRead)); // get everything
1189 // Read full characters and leave partial input in the buffer. Encoding.GetCharCount doesn't work because
1190 // it returns fallback characters on partial input, meaning that it overcounts. Instead, we use
1191 // GetCharCount from the decoder and tell it to preserve state, so that it returns the count of full
1192 // characters. Note that we don't actually want it to preserve state, so we call the decoder as if it's
1193 // preserving state and then call Reset in between calls. This uses a local decoder instead of the class
1194 // member decoder because that one may preserve state across SerialPort method calls.
1195 Decoder localDecoder = Encoding.GetDecoder();
1196 int numCharsReceived = localDecoder.GetCharCount(bytesReceived, 0, bytesReceived.Length);
1197 int lastFullCharIndex = bytesReceived.Length;
1199 if (numCharsReceived == 0)
1201 Buffer.BlockCopy(bytesReceived, 0, inBuffer, 0, bytesReceived.Length); // put it all back!
1202 // don't change readPos. --> readPos == 0?
1204 readLen = bytesReceived.Length;
1210 localDecoder.Reset();
1211 lastFullCharIndex--;
1212 } while (localDecoder.GetCharCount(bytesReceived, 0, lastFullCharIndex) == numCharsReceived);
1215 readLen = bytesReceived.Length - (lastFullCharIndex + 1);
1217 Buffer.BlockCopy(bytesReceived, lastFullCharIndex + 1, inBuffer, 0, bytesReceived.Length - (lastFullCharIndex + 1));
1218 return Encoding.GetString(bytesReceived, 0, lastFullCharIndex + 1);
1221 public string ReadLine() {
1222 return ReadTo(NewLine);
1225 public string ReadTo(string value)
1228 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1230 throw new ArgumentNullException("value");
1231 if (value.Length == 0)
1232 throw new ArgumentException(SR.GetString(SR.InvalidNullEmptyArgument, "value"));
1234 int startTicks = Environment.TickCount;
1238 StringBuilder currentLine = new StringBuilder();
1239 char lastValueChar = value[value.Length-1];
1241 // for timeout issues, best to read everything already on the stream into our buffers.
1242 // first make sure inBuffer is big enough
1243 int bytesInStream = internalSerialStream.BytesToRead;
1244 MaybeResizeBuffer(bytesInStream);
1246 readLen += internalSerialStream.Read(inBuffer, readLen, bytesInStream);
1247 int beginReadPos = readPos;
1249 if (singleCharBuffer == null) {
1250 // This is somewhat of an approximate guesstimate to get the max char[] size needed to encode a single character
1251 singleCharBuffer = new char[maxByteCountForSingleChar];
1257 if(readTimeout == InfiniteTimeout) {
1258 numCharsRead = InternalRead(singleCharBuffer, 0, 1, readTimeout, true);
1260 else if (readTimeout - timeUsed >= 0) {
1261 timeNow = Environment.TickCount;
1262 numCharsRead = InternalRead(singleCharBuffer, 0, 1, readTimeout - timeUsed, true);
1263 timeUsed += Environment.TickCount - timeNow;
1266 throw new TimeoutException();
1269 if (numCharsRead > 1) {
1270 for (int i=0; i<numCharsRead; i++)
1271 Debug.Assert((Char.IsSurrogate(singleCharBuffer[i])), "number of chars read should be more than one only for surrogate characters!");
1274 Debug.Assert((numCharsRead > 0), "possible bug in ReadBufferIntoChars, reading surrogate char?");
1275 currentLine.Append(singleCharBuffer, 0, numCharsRead);
1277 if (lastValueChar == (char) singleCharBuffer[numCharsRead-1] && (currentLine.Length >= value.Length)) {
1278 // we found the last char in the value string. See if the rest is there. No need to
1279 // recompare the last char of the value string.
1281 for (int i=2; i<=value.Length; i++) {
1282 if (value[value.Length-i] != currentLine[currentLine.Length-i]) {
1289 // we found the search string. Exclude it from the return string.
1290 string ret = currentLine.ToString(0, currentLine.Length - value.Length);
1291 if (readPos == readLen) readPos = readLen = 0;
1298 // We probably got here due to timeout.
1299 // We will try our best to restore the internal states, it's tricky!
1301 // 0) Save any existing data
1302 // 1) Restore readPos to the original position upon entering ReadTo
1303 // 2) Set readLen to the number of bytes read since entering ReadTo
1304 // 3) Restore inBuffer so that it contains the bytes from currentLine, resizing if necessary.
1305 // 4) Append the buffer with any saved data from 0)
1307 byte[] readBuffer = encoding.GetBytes(currentLine.ToString());
1309 // We will compact the data by default
1310 if (readBuffer.Length > 0) {
1311 int bytesToSave = CachedBytesToRead;
1312 byte[] savBuffer = new byte[bytesToSave];
1314 if (bytesToSave > 0)
1315 Buffer.BlockCopy(inBuffer, readPos, savBuffer, 0, bytesToSave);
1320 MaybeResizeBuffer(readBuffer.Length + bytesToSave);
1322 Buffer.BlockCopy(readBuffer, 0, inBuffer, readLen, readBuffer.Length);
1323 readLen += readBuffer.Length;
1325 if (bytesToSave > 0) {
1326 Buffer.BlockCopy(savBuffer, 0, inBuffer, readLen, bytesToSave);
1327 readLen += bytesToSave;
1335 // Writes string to output, no matter string's length.
1336 public void Write(string text)
1339 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1341 throw new ArgumentNullException("text");
1342 if (text.Length == 0) return;
1343 byte [] bytesToWrite;
1345 bytesToWrite = encoding.GetBytes(text);
1347 internalSerialStream.Write(bytesToWrite, 0, bytesToWrite.Length, writeTimeout);
1350 // encoding-dependent Write-chars method.
1351 // Probably as performant as direct conversion from ASCII to bytes, since we have to cast anyway (we can just call GetBytes)
1352 public void Write(char[] buffer, int offset, int count) {
1354 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1356 throw new ArgumentNullException("buffer");
1358 throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
1360 throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
1361 if (buffer.Length - offset < count)
1362 throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
1364 if (buffer.Length == 0) return;
1366 byte [] byteArray = Encoding.GetBytes(buffer,offset, count);
1367 Write(byteArray, 0, byteArray.Length);
1371 // Writes a specified section of a byte buffer to output.
1372 public void Write(byte[] buffer, int offset, int count)
1375 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1377 throw new ArgumentNullException("buffer", SR.GetString(SR.ArgumentNull_Buffer));
1379 throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
1381 throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
1382 if (buffer.Length - offset < count)
1383 throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
1384 if (buffer.Length == 0) return;
1386 internalSerialStream.Write(buffer, offset, count, writeTimeout);
1389 public void WriteLine(string text) {
1390 Write(text + NewLine);
1394 // ----- SECTION: internal utility methods ----------------*
1396 // included here just to use the event filter to block unwanted invocations of the Serial Port's events.
1397 // Plus, this enforces the requirement on the received event that the number of buffered bytes >= receivedBytesThreshold
1398 private void CatchErrorEvents(object src, SerialErrorReceivedEventArgs e)
1400 SerialErrorReceivedEventHandler eventHandler = ErrorReceived;
1401 SerialStream stream = internalSerialStream;
1403 if ((eventHandler != null) && (stream != null)){
1406 eventHandler(this, e);
1411 private void CatchPinChangedEvents(object src, SerialPinChangedEventArgs e)
1413 SerialPinChangedEventHandler eventHandler = PinChanged;
1414 SerialStream stream = internalSerialStream;
1416 if ((eventHandler != null) && (stream != null)){
1419 eventHandler(this, e);
1424 private void CatchReceivedEvents(object src, SerialDataReceivedEventArgs e)
1426 SerialDataReceivedEventHandler eventHandler = DataReceived;
1427 SerialStream stream = internalSerialStream;
1429 if ((eventHandler != null) && (stream != null)){
1431 // SerialStream might be closed between the time the event runner
1432 // pumped this event and the time the threadpool thread end up
1433 // invoking this event handler. The above lock and IsOpen check
1434 // ensures that we raise the event only when the port is open
1436 bool raiseEvent = false;
1438 raiseEvent = stream.IsOpen && (SerialData.Eof == e.EventType || BytesToRead >= receivedBytesThreshold);
1441 // Ignore and continue. SerialPort might have been closed already!
1445 eventHandler(this, e); // here, do your reading, etc.
1451 private void CompactBuffer()
1453 Buffer.BlockCopy(inBuffer, readPos, inBuffer, 0, CachedBytesToRead);
1454 readLen = CachedBytesToRead;
1458 // This method guarantees that our inBuffer is big enough. The parameter passed in is
1459 // the number of bytes that our code is going to add to inBuffer. MaybeResizeBuffer will
1460 // do one of three things depending on how much data is already in the buffer and how
1461 // much will be added:
1462 // 1) Nothing. The current buffer is big enough to hold it all
1463 // 2) Compact the existing data and keep the current buffer.
1464 // 3) Create a new, larger buffer and compact the existing data into it.
1465 private void MaybeResizeBuffer(int additionalByteLength)
1467 // Case 1. No action needed
1468 if (additionalByteLength + readLen <= inBuffer.Length)
1472 if (CachedBytesToRead + additionalByteLength <= inBuffer.Length / 2)
1475 // Case 3. Create a new buffer
1476 int newLength = Math.Max(CachedBytesToRead + additionalByteLength, inBuffer.Length * 2);
1478 Debug.Assert(inBuffer.Length >= readLen, "ResizeBuffer - readLen > inBuffer.Length");
1479 byte[] newBuffer = new byte[newLength];
1480 // only copy the valid data from inBuffer, and put it at the beginning of newBuffer.
1481 Buffer.BlockCopy(inBuffer, readPos, newBuffer, 0, CachedBytesToRead);
1482 readLen = CachedBytesToRead;
1484 inBuffer = newBuffer;
1488 private static int GetElapsedTime(int currentTickCount, int startTickCount)
1490 int elapsedTime = unchecked(currentTickCount - startTickCount);
1491 return (elapsedTime >= 0) ? (int)elapsedTime : Int32.MaxValue;