// // System.IO.Ports.WinSerialStream.cs // // Authors: // Carlos Alberto Cortez (calberto.cortez@gmail.com) // // (c) Copyright 2006 Novell, Inc. (http://www.novell.com) // using System; using System.Text; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.ComponentModel; namespace System.IO.Ports { class WinSerialStream : Stream, ISerialStream, IDisposable { // Windows API Constants const uint GenericRead = 0x80000000; const uint GenericWrite = 0x40000000; const uint OpenExisting = 3; const uint FileFlagOverlapped = 0x40000000; const uint PurgeRxClear = 0x0008; const uint PurgeTxClear = 0x0004; const uint WinInfiniteTimeout = 0xFFFFFFFF; const uint FileIOPending = 997; // Signal constants const uint SetRts = 3; const uint ClearRts = 4; const uint SetDtr = 5; const uint ClearDtr = 6; const uint SetBreak = 8; const uint ClearBreak = 9; const uint CtsOn = 0x0010; const uint DsrOn = 0x0020; const uint RsldOn = 0x0080; // Event constants const uint EvRxChar = 0x0001; const uint EvCts = 0x0008; const uint EvDsr = 0x0010; const uint EvRlsd = 0x0020; const uint EvBreak = 0x0040; const uint EvErr = 0x0080; const uint EvRing = 0x0100; int handle; int read_timeout; int write_timeout; bool disposed; IntPtr write_overlapped; IntPtr read_overlapped; ManualResetEvent read_event; ManualResetEvent write_event; Timeouts timeouts; [DllImport("kernel32", SetLastError = true)] static extern int CreateFile(string port_name, uint desired_access, uint share_mode, uint security_attrs, uint creation, uint flags, uint template); [DllImport("kernel32", SetLastError = true)] static extern bool SetupComm(int handle, int read_buffer_size, int write_buffer_size); [DllImport("kernel32", SetLastError = true)] static extern bool PurgeComm(int handle, uint flags); [DllImport("kernel32", SetLastError = true)] static extern bool SetCommTimeouts(int handle, Timeouts timeouts); public WinSerialStream (string port_name, int baud_rate, int data_bits, Parity parity, StopBits sb, bool dtr_enable, bool rts_enable, Handshake hs, int read_timeout, int write_timeout, int read_buffer_size, int write_buffer_size) { handle = CreateFile (port_name != null && !port_name.StartsWith(@"\\.\") ? @"\\.\" + port_name : port_name, GenericRead | GenericWrite, 0, 0, OpenExisting, FileFlagOverlapped, 0); if (handle == -1) ReportIOError (port_name); // Set port low level attributes SetAttributes (baud_rate, parity, data_bits, sb, hs); // Clean buffers and set sizes if (!PurgeComm (handle, PurgeRxClear | PurgeTxClear) || !SetupComm (handle, read_buffer_size, write_buffer_size)) ReportIOError (null); // Set timeouts this.read_timeout = read_timeout; this.write_timeout = write_timeout; timeouts = new Timeouts (read_timeout, write_timeout); if (!SetCommTimeouts(handle, timeouts)) ReportIOError (null); /// Set DTR and RTS SetSignal(SerialSignal.Dtr, dtr_enable); if (hs != Handshake.RequestToSend && hs != Handshake.RequestToSendXOnXOff) SetSignal(SerialSignal.Rts, rts_enable); // Init overlapped structures NativeOverlapped wo = new NativeOverlapped (); write_event = new ManualResetEvent (false); #pragma warning disable 618 wo.EventHandle = write_event.Handle; write_overlapped = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (NativeOverlapped))); Marshal.StructureToPtr (wo, write_overlapped, true); NativeOverlapped ro = new NativeOverlapped (); read_event = new ManualResetEvent (false); ro.EventHandle = read_event.Handle; read_overlapped = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (NativeOverlapped))); Marshal.StructureToPtr (ro, read_overlapped, true); #pragma warning restore 618 } public override bool CanRead { get { return true; } } public override bool CanSeek { get { return false; } } public override bool CanTimeout { get { return true; } } public override bool CanWrite { get { return true; } } public override int ReadTimeout { get { return read_timeout; } set { if (value < 0 && value != SerialPort.InfiniteTimeout) throw new ArgumentOutOfRangeException ("value"); timeouts.SetValues (value, write_timeout); if (!SetCommTimeouts (handle, timeouts)) ReportIOError (null); read_timeout = value; } } public override int WriteTimeout { get { return write_timeout; } set { if (value < 0 && value != SerialPort.InfiniteTimeout) throw new ArgumentOutOfRangeException ("value"); timeouts.SetValues (read_timeout, value); if (!SetCommTimeouts (handle, timeouts)) ReportIOError (null); write_timeout = value; } } public override long Length { get { throw new NotSupportedException (); } } public override long Position { get { throw new NotSupportedException (); } set { throw new NotSupportedException (); } } [DllImport("kernel32", SetLastError = true)] static extern bool CloseHandle (int handle); protected override void Dispose (bool disposing) { if (disposed) return; disposed = true; CloseHandle (handle); Marshal.FreeHGlobal (write_overlapped); Marshal.FreeHGlobal (read_overlapped); } void IDisposable.Dispose () { Dispose (true); GC.SuppressFinalize (this); } public override void Close () { ((IDisposable)this).Dispose (); } ~WinSerialStream () { Dispose (false); } public override void Flush () { CheckDisposed (); // No dothing by now } public override long Seek (long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength (long value) { throw new NotSupportedException(); } [DllImport("kernel32", SetLastError = true)] static extern unsafe bool ReadFile (int handle, byte* buffer, int bytes_to_read, out int bytes_read, IntPtr overlapped); [DllImport("kernel32", SetLastError = true)] static extern unsafe bool GetOverlappedResult (int handle, IntPtr overlapped, ref int bytes_transfered, bool wait); public override int Read ([In, Out] byte [] buffer, int offset, int count) { CheckDisposed (); if (buffer == null) throw new ArgumentNullException ("buffer"); if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException ("offset or count less than zero."); if (buffer.Length - offset < count ) throw new ArgumentException ("offset+count", "The size of the buffer is less than offset + count."); int bytes_read; unsafe { fixed (byte* ptr = buffer) { if (ReadFile (handle, ptr + offset, count, out bytes_read, read_overlapped)) return bytes_read; // Test for overlapped behavior if (Marshal.GetLastWin32Error () != FileIOPending) ReportIOError (null); if (!GetOverlappedResult (handle, read_overlapped, ref bytes_read, true)) ReportIOError (null); } } if (bytes_read == 0) throw new TimeoutException (); // We didn't get any byte return bytes_read; } [DllImport("kernel32", SetLastError = true)] static extern unsafe bool WriteFile (int handle, byte* buffer, int bytes_to_write, out int bytes_written, IntPtr overlapped); public override void Write (byte [] buffer, int offset, int count) { CheckDisposed (); if (buffer == null) throw new ArgumentNullException ("buffer"); if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException (); if (buffer.Length - offset < count) throw new ArgumentException ("offset+count", "The size of the buffer is less than offset + count."); int bytes_written = 0; unsafe { fixed (byte* ptr = buffer) { if (WriteFile (handle, ptr + offset, count, out bytes_written, write_overlapped)) return; if (Marshal.GetLastWin32Error() != FileIOPending) ReportIOError (null); if (!GetOverlappedResult(handle, write_overlapped, ref bytes_written, true)) ReportIOError (null); } } // If the operation timed out, then // we transfered less bytes than the requested ones if (bytes_written < count) throw new TimeoutException (); } [DllImport("kernel32", SetLastError = true)] static extern bool GetCommState (int handle, [Out] DCB dcb); [DllImport ("kernel32", SetLastError=true)] static extern bool SetCommState (int handle, DCB dcb); public void SetAttributes (int baud_rate, Parity parity, int data_bits, StopBits bits, Handshake hs) { DCB dcb = new DCB (); if (!GetCommState (handle, dcb)) ReportIOError (null); dcb.SetValues (baud_rate, parity, data_bits, bits, hs); if (!SetCommState (handle, dcb)) ReportIOError (null); } void ReportIOError(string optional_arg) { int error = Marshal.GetLastWin32Error (); string message; switch (error) { case 2: case 3: message = "The port `" + optional_arg + "' does not exist."; break; case 87: message = "Parameter is incorrect."; break; default: // As fallback, we show the win32 error message = new Win32Exception ().Message; break; } throw new IOException (message); } void CheckDisposed () { if (disposed) throw new ObjectDisposedException (GetType ().FullName); } // ISerialStream members public void DiscardInBuffer () { if (!PurgeComm (handle, PurgeRxClear)) ReportIOError (null); } public void DiscardOutBuffer () { if (!PurgeComm (handle, PurgeTxClear)) ReportIOError (null); } [DllImport ("kernel32", SetLastError=true)] static extern bool ClearCommError (int handle, out uint errors, out CommStat stat); public int BytesToRead { get { uint errors; CommStat stat; if (!ClearCommError (handle, out errors, out stat)) ReportIOError (null); return (int)stat.BytesIn; } } public int BytesToWrite { get { uint errors; CommStat stat; if (!ClearCommError (handle, out errors, out stat)) ReportIOError (null); return (int)stat.BytesOut; } } [DllImport ("kernel32", SetLastError=true)] static extern bool GetCommModemStatus (int handle, out uint flags); public SerialSignal GetSignals () { uint flags; if (!GetCommModemStatus (handle, out flags)) ReportIOError (null); SerialSignal signals = SerialSignal.None; if ((flags & RsldOn) != 0) signals |= SerialSignal.Cd; if ((flags & CtsOn) != 0) signals |= SerialSignal.Cts; if ((flags & DsrOn) != 0) signals |= SerialSignal.Dsr; return signals; } [DllImport ("kernel32", SetLastError=true)] static extern bool EscapeCommFunction (int handle, uint flags); public void SetSignal (SerialSignal signal, bool value) { if (signal != SerialSignal.Rts && signal != SerialSignal.Dtr) throw new Exception ("Wrong internal value"); uint flag; if (signal == SerialSignal.Rts) if (value) flag = SetRts; else flag = ClearRts; else if (value) flag = SetDtr; else flag = ClearDtr; if (!EscapeCommFunction (handle, flag)) ReportIOError (null); } public void SetBreakState (bool value) { if (!EscapeCommFunction (handle, value ? SetBreak : ClearBreak)) ReportIOError (null); } } [StructLayout (LayoutKind.Sequential)] class DCB { public int dcb_length; public int baud_rate; public int flags; public short w_reserved; public short xon_lim; public short xoff_lim; public byte byte_size; public byte parity; public byte stop_bits; public byte xon_char; public byte xoff_char; public byte error_char; public byte eof_char; public byte evt_char; public short w_reserved1; // flags: //const int fBinary = 0x0001; //const int fParity = 0x0002; const int fOutxCtsFlow = 0x0004; //const int fOutxDsrFlow1 = 0x0008; //const int fOutxDsrFlow2 = 0x0010; //const int fDtrControl = 0x00020; //const int fDsrSensitivity = 0x0040; //const int fTXContinueOnXoff = 0x0080; const int fOutX = 0x0100; const int fInX = 0x0200; //const int fErrorChar = 0x0400; //const int fNull = 0x0800; //const int fRtsControl1 = 0x1000; const int fRtsControl2 = 0x2000; //const int fAbortOnError = 0x4000; public void SetValues (int baud_rate, Parity parity, int byte_size, StopBits sb, Handshake hs) { switch (sb) { case StopBits.One: stop_bits = 0; break; case StopBits.OnePointFive: stop_bits = 1; break; case StopBits.Two: stop_bits = 2; break; default: // Shouldn't happen break; } this.baud_rate = baud_rate; this.parity = (byte)parity; this.byte_size = (byte)byte_size; // Clear Handshake flags flags &= ~(fOutxCtsFlow | fOutX | fInX | fRtsControl2); // Set Handshake flags switch (hs) { case Handshake.None: break; case Handshake.XOnXOff: flags |= fOutX | fInX; break; case Handshake.RequestToSend: flags |= fOutxCtsFlow | fRtsControl2; break; case Handshake.RequestToSendXOnXOff: flags |= fOutxCtsFlow | fOutX | fInX | fRtsControl2; break; default: // Shouldn't happen break; } } } [StructLayout (LayoutKind.Sequential)] class Timeouts { public uint ReadIntervalTimeout; public uint ReadTotalTimeoutMultiplier; public uint ReadTotalTimeoutConstant; public uint WriteTotalTimeoutMultiplier; public uint WriteTotalTimeoutConstant; public const uint MaxDWord = 0xFFFFFFFF; public Timeouts (int read_timeout, int write_timeout) { SetValues (read_timeout, write_timeout); } public void SetValues (int read_timeout, int write_timeout) { // FIXME: The windows api docs are not very clear about read timeouts, // and we have to simulate infinite with a big value (uint.MaxValue - 1) ReadIntervalTimeout = MaxDWord; ReadTotalTimeoutMultiplier = MaxDWord; ReadTotalTimeoutConstant = (read_timeout == -1 ? MaxDWord - 1 : (uint) read_timeout); WriteTotalTimeoutMultiplier = 0; WriteTotalTimeoutConstant = (write_timeout == -1 ? MaxDWord : (uint) write_timeout); } } [StructLayout (LayoutKind.Sequential)] struct CommStat { public uint flags; public uint BytesIn; public uint BytesOut; } }