Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- public interface ISerialPortIo : IDisposable
- {
- string PortName { get; }
- string ReadLine();
- void WriteLine(string text);
- }
- public class SerialPortConfig
- {
- public string Name { get; private set; }
- public int BaudRate { get; private set; }
- public int DataBits { get; private set; }
- public StopBits StopBits { get; private set; }
- public Parity Parity { get; private set; }
- public bool DtrEnable { get; private set; }
- public bool RtsEnable { get; private set; }
- public SerialPortConfig(
- string name,
- int baudRate,
- int dataBits,
- StopBits stopBits,
- Parity parity,
- bool dtrEnable,
- bool rtsEnable)
- {
- if (String.IsNullOrWhiteSpace(name)) throw new ArgumentNullException("name");
- this.RtsEnable = rtsEnable;
- this.BaudRate = baudRate;
- this.DataBits = dataBits;
- this.StopBits = stopBits;
- this.Parity = parity;
- this.DtrEnable = dtrEnable;
- this.Name = name;
- }
- public override string ToString()
- {
- return String.Format(
- "{0} (Baud: {1}/DataBits: {2}/Parity: {3}/StopBits: {4}/{5})",
- this.Name,
- this.BaudRate,
- this.DataBits,
- this.Parity,
- this.StopBits,
- this.RtsEnable ? "RTS" : "No RTS");
- }
- }
- // Wrapper around SerialPort
- public class SerialPortIo : ISerialPortIo
- {
- protected ILog Log { get; private set; }
- static readonly ILog s_Log = LogManager.GetLogger(typeof(SerialPortIo));
- readonly SerialPort _port;
- readonly Stream _internalSerialStream;
- public SerialPortIo(SerialPortConfig portConfig)
- {
- if (portConfig == null) throw new ArgumentNullException("portConfig");
- this.Log = LogManager.GetLogger(this.GetType());
- // http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html
- SerialPortFixer.Execute(portConfig.Name);
- var port = new SerialPort(
- portConfig.Name,
- portConfig.BaudRate,
- portConfig.Parity,
- portConfig.DataBits,
- portConfig.StopBits)
- {
- RtsEnable = portConfig.RtsEnable,
- DtrEnable = portConfig.DtrEnable,
- ReadTimeout = 5000,
- WriteTimeout = 5000
- };
- port.Open();
- try
- {
- this._internalSerialStream = port.BaseStream;
- this._port = port;
- this._port.DiscardInBuffer();
- this._port.DiscardOutBuffer();
- }
- catch (Exception ex)
- {
- Stream internalStream = this._internalSerialStream;
- if (internalStream == null)
- {
- FieldInfo field = typeof(SerialPort).GetField(
- "internalSerialStream",
- BindingFlags.Instance | BindingFlags.NonPublic);
- // This will happen if the SerialPort class is changed
- // in future versions of the .NET Framework
- if (field == null)
- {
- this.Log.WarnFormat(
- "An exception occured while creating the serial port adaptor, "
- + "the internal stream reference was not acquired and we were unable "
- + "to get it using reflection. The serial port may not be accessible "
- + "any further until the serial port object finalizer has been run: {0}",
- ex);
- throw;
- }
- internalStream = (Stream)field.GetValue(port);
- }
- this.Log.DebugFormat(
- "An error occurred while constructing the serial port adaptor: {0}", ex);
- SafeDisconnect(port, internalStream);
- throw;
- }
- }
- public string PortName
- {
- get { return this._port.PortName; }
- }
- public string ReadLine()
- {
- return this._port.ReadTo(Environment.NewLine);
- }
- public void WriteLine(string text)
- {
- this._port.Write(text);
- this._port.Write("\r");
- }
- public void Dispose()
- {
- this.Dispose(true);
- }
- protected void Dispose(bool disposing)
- {
- SafeDisconnect(this._port, this._internalSerialStream);
- if (disposing)
- {
- GC.SuppressFinalize(this);
- }
- }
- /// <summary>
- /// Safely closes a serial port and its internal stream even if
- /// a USB serial interface was physically removed from the system
- /// in a reliable manner.
- /// </summary>
- /// <param name="port"></param>
- /// <param name="internalSerialStream"></param>
- /// <remarks>
- /// The <see cref="SerialPort"/> class has 3 different problems in disposal
- /// in case of a USB serial device that is physically removed:
- ///
- /// 1. The eventLoopRunner is asked to stop and <see cref="SerialPort.IsOpen"/>
- /// returns false. Upon disposal this property is checked and closing
- /// the internal serial stream is skipped, thus keeping the original
- /// handle open indefinitely (until the finalizer runs which leads to the next problem)
- ///
- /// The solution for this one is to manually close the internal serial stream.
- /// We can get its reference by <see cref="SerialPort.BaseStream" />
- /// before the exception has happened or by reflection and getting the
- /// "internalSerialStream" field.
- ///
- /// 2. Closing the internal serial stream throws an exception and closes
- /// the internal handle without waiting for its eventLoopRunner thread to finish,
- /// causing an uncatchable ObjectDisposedException from it later on when the finalizer
- /// runs (which oddly avoids throwing the exception but still fails to wait for
- /// the eventLoopRunner).
- ///
- /// The solution is to manually ask the event loop runner thread to shutdown
- /// (via reflection) and waiting for it before closing the internal serial stream.
- ///
- /// 3. Since Dispose throws exceptions, the finalizer is not suppressed.
- ///
- /// The solution is to suppress their finalizers at the beginning.
- /// </remarks>
- static void SafeDisconnect(SerialPort port, Stream internalSerialStream)
- {
- GC.SuppressFinalize(port);
- GC.SuppressFinalize(internalSerialStream);
- ShutdownEventLoopHandler(internalSerialStream);
- try
- {
- s_Log.DebugFormat("Disposing internal serial stream");
- internalSerialStream.Close();
- }
- catch (Exception ex)
- {
- s_Log.DebugFormat(
- "Exception in serial stream shutdown of port {0}: {1}", port.PortName, ex);
- }
- try
- {
- s_Log.DebugFormat("Disposing serial port");
- port.Close();
- }
- catch (Exception ex)
- {
- s_Log.DebugFormat("Exception in port {0} shutdown: {1}", port.PortName, ex);
- }
- }
- static void ShutdownEventLoopHandler(Stream internalSerialStream)
- {
- try
- {
- s_Log.DebugFormat("Working around .NET SerialPort class Dispose bug");
- FieldInfo eventRunnerField = internalSerialStream.GetType()
- .GetField("eventRunner", BindingFlags.NonPublic | BindingFlags.Instance);
- if (eventRunnerField == null)
- {
- s_Log.WarnFormat(
- "Unable to find EventLoopRunner field. "
- + "SerialPort workaround failure. Application may crash after "
- + "disposing SerialPort unless .NET 1.1 unhandled exception "
- + "policy is enabled from the application's config file.");
- }
- else
- {
- object eventRunner = eventRunnerField.GetValue(internalSerialStream);
- Type eventRunnerType = eventRunner.GetType();
- FieldInfo endEventLoopFieldInfo = eventRunnerType.GetField(
- "endEventLoop", BindingFlags.Instance | BindingFlags.NonPublic);
- FieldInfo eventLoopEndedSignalFieldInfo = eventRunnerType.GetField(
- "eventLoopEndedSignal", BindingFlags.Instance | BindingFlags.NonPublic);
- FieldInfo waitCommEventWaitHandleFieldInfo = eventRunnerType.GetField(
- "waitCommEventWaitHandle", BindingFlags.Instance | BindingFlags.NonPublic);
- if (endEventLoopFieldInfo == null
- || eventLoopEndedSignalFieldInfo == null
- || waitCommEventWaitHandleFieldInfo == null)
- {
- s_Log.WarnFormat(
- "Unable to find the EventLoopRunner internal wait handle or loop signal fields. "
- + "SerialPort workaround failure. Application may crash after "
- + "disposing SerialPort unless .NET 1.1 unhandled exception "
- + "policy is enabled from the application's config file.");
- }
- else
- {
- s_Log.DebugFormat(
- "Waiting for the SerialPort internal EventLoopRunner thread to finish...");
- var eventLoopEndedWaitHandle =
- (WaitHandle)eventLoopEndedSignalFieldInfo.GetValue(eventRunner);
- var waitCommEventWaitHandle =
- (ManualResetEvent)waitCommEventWaitHandleFieldInfo.GetValue(eventRunner);
- endEventLoopFieldInfo.SetValue(eventRunner, true);
- // Sometimes the event loop handler resets the wait handle
- // before exiting the loop and hangs (in case of USB disconnect)
- // In case it takes too long, brute-force it out of its wait by
- // setting the handle again.
- do
- {
- waitCommEventWaitHandle.Set();
- } while (!eventLoopEndedWaitHandle.WaitOne(2000));
- s_Log.DebugFormat("Wait completed. Now it is safe to continue disposal.");
- }
- }
- }
- catch (Exception ex)
- {
- s_Log.ErrorFormat(
- "SerialPort workaround failure. Application may crash after "
- + "disposing SerialPort unless .NET 1.1 unhandled exception "
- + "policy is enabled from the application's config file: {0}",
- ex);
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement