Pastebin launched a little side project called VERYVIRAL.com, check it out ;-) Want more features on Pastebin? Sign Up, it's FREE!
Guest

SerialPortIo

By: 3psil0N on Jan 29th, 2012  |  syntax: C#  |  size: 10.63 KB  |  views: 663  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. public interface ISerialPortIo : IDisposable
  2. {
  3.     string PortName { get; }
  4.     string ReadLine();
  5.     void WriteLine(string text);
  6. }
  7.  
  8. public class SerialPortConfig
  9. {
  10.     public string Name { get; private set; }
  11.     public int BaudRate { get; private set; }
  12.     public int DataBits { get; private set; }
  13.     public StopBits StopBits { get; private set; }
  14.     public Parity Parity { get; private set; }
  15.     public bool DtrEnable { get; private set; }
  16.     public bool RtsEnable { get; private set; }
  17.  
  18.     public SerialPortConfig(
  19.         string name,
  20.         int baudRate,
  21.         int dataBits,
  22.         StopBits stopBits,
  23.         Parity parity,
  24.         bool dtrEnable,
  25.         bool rtsEnable)
  26.     {
  27.         if (String.IsNullOrWhiteSpace(name)) throw new ArgumentNullException("name");
  28.  
  29.         this.RtsEnable = rtsEnable;
  30.         this.BaudRate = baudRate;
  31.         this.DataBits = dataBits;
  32.         this.StopBits = stopBits;
  33.         this.Parity = parity;
  34.         this.DtrEnable = dtrEnable;
  35.         this.Name = name;
  36.     }
  37.  
  38.     public override string ToString()
  39.     {
  40.         return String.Format(
  41.             "{0} (Baud: {1}/DataBits: {2}/Parity: {3}/StopBits: {4}/{5})",
  42.             this.Name,
  43.             this.BaudRate,
  44.             this.DataBits,
  45.             this.Parity,
  46.             this.StopBits,
  47.             this.RtsEnable ? "RTS" : "No RTS");
  48.     }
  49. }
  50.  
  51. // Wrapper around SerialPort
  52. public class SerialPortIo : ISerialPortIo
  53. {
  54.     protected ILog Log { get; private set; }
  55.     static readonly ILog s_Log = LogManager.GetLogger(typeof(SerialPortIo));
  56.  
  57.     readonly SerialPort _port;
  58.     readonly Stream _internalSerialStream;
  59.  
  60.     public SerialPortIo(SerialPortConfig portConfig)
  61.     {
  62.         if (portConfig == null) throw new ArgumentNullException("portConfig");
  63.  
  64.         this.Log = LogManager.GetLogger(this.GetType());
  65.  
  66.         // http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html
  67.         SerialPortFixer.Execute(portConfig.Name);
  68.  
  69.         var port = new SerialPort(
  70.             portConfig.Name,
  71.             portConfig.BaudRate,
  72.             portConfig.Parity,
  73.             portConfig.DataBits,
  74.             portConfig.StopBits)
  75.         {
  76.             RtsEnable = portConfig.RtsEnable,
  77.             DtrEnable = portConfig.DtrEnable,
  78.             ReadTimeout = 5000,
  79.             WriteTimeout = 5000
  80.         };
  81.         port.Open();
  82.  
  83.         try
  84.         {
  85.             this._internalSerialStream = port.BaseStream;
  86.             this._port = port;
  87.             this._port.DiscardInBuffer();
  88.             this._port.DiscardOutBuffer();
  89.         }
  90.         catch (Exception ex)
  91.         {
  92.             Stream internalStream = this._internalSerialStream;
  93.  
  94.             if (internalStream == null)
  95.             {
  96.                 FieldInfo field = typeof(SerialPort).GetField(
  97.                     "internalSerialStream",
  98.                     BindingFlags.Instance | BindingFlags.NonPublic);
  99.  
  100.                 // This will happen if the SerialPort class is changed
  101.                 // in future versions of the .NET Framework
  102.                 if (field == null)
  103.                 {
  104.                     this.Log.WarnFormat(
  105.                         "An exception occured while creating the serial port adaptor, "
  106.                         + "the internal stream reference was not acquired and we were unable "
  107.                         + "to get it using reflection. The serial port may not be accessible "
  108.                         + "any further until the serial port object finalizer has been run: {0}",
  109.                         ex);
  110.  
  111.                     throw;
  112.                 }
  113.  
  114.                 internalStream = (Stream)field.GetValue(port);
  115.             }
  116.  
  117.             this.Log.DebugFormat(
  118.                 "An error occurred while constructing the serial port adaptor: {0}", ex);
  119.  
  120.             SafeDisconnect(port, internalStream);
  121.             throw;
  122.         }
  123.     }
  124.  
  125.     public string PortName
  126.     {
  127.         get { return this._port.PortName; }
  128.     }
  129.  
  130.     public string ReadLine()
  131.     {
  132.         return this._port.ReadTo(Environment.NewLine);
  133.     }
  134.  
  135.     public void WriteLine(string text)
  136.     {
  137.         this._port.Write(text);
  138.         this._port.Write("\r");
  139.     }
  140.  
  141.     public void Dispose()
  142.     {
  143.         this.Dispose(true);
  144.     }
  145.  
  146.     protected void Dispose(bool disposing)
  147.     {
  148.         SafeDisconnect(this._port, this._internalSerialStream);
  149.  
  150.         if (disposing)
  151.         {
  152.             GC.SuppressFinalize(this);
  153.         }
  154.     }
  155.  
  156.     /// <summary>
  157.     /// Safely closes a serial port and its internal stream even if
  158.     /// a USB serial interface was physically removed from the system
  159.     /// in a reliable manner.
  160.     /// </summary>
  161.     /// <param name="port"></param>
  162.     /// <param name="internalSerialStream"></param>
  163.     /// <remarks>
  164.     /// The <see cref="SerialPort"/> class has 3 different problems in disposal
  165.     /// in case of a USB serial device that is physically removed:
  166.     ///
  167.     /// 1. The eventLoopRunner is asked to stop and <see cref="SerialPort.IsOpen"/>
  168.     /// returns false. Upon disposal this property is checked and closing
  169.     /// the internal serial stream is skipped, thus keeping the original
  170.     /// handle open indefinitely (until the finalizer runs which leads to the next problem)
  171.     ///
  172.     /// The solution for this one is to manually close the internal serial stream.
  173.     /// We can get its reference by <see cref="SerialPort.BaseStream" />
  174.     /// before the exception has happened or by reflection and getting the
  175.     /// "internalSerialStream" field.
  176.     ///
  177.     /// 2. Closing the internal serial stream throws an exception and closes
  178.     /// the internal handle without waiting for its eventLoopRunner thread to finish,
  179.     /// causing an uncatchable ObjectDisposedException from it later on when the finalizer
  180.     /// runs (which oddly avoids throwing the exception but still fails to wait for
  181.     /// the eventLoopRunner).
  182.     ///
  183.     /// The solution is to manually ask the event loop runner thread to shutdown
  184.     /// (via reflection) and waiting for it before closing the internal serial stream.
  185.     ///
  186.     /// 3. Since Dispose throws exceptions, the finalizer is not suppressed.
  187.     ///
  188.     /// The solution is to suppress their finalizers at the beginning.
  189.     /// </remarks>
  190.     static void SafeDisconnect(SerialPort port, Stream internalSerialStream)
  191.     {
  192.         GC.SuppressFinalize(port);
  193.         GC.SuppressFinalize(internalSerialStream);
  194.  
  195.         ShutdownEventLoopHandler(internalSerialStream);
  196.  
  197.         try
  198.         {
  199.             s_Log.DebugFormat("Disposing internal serial stream");
  200.             internalSerialStream.Close();
  201.         }
  202.         catch (Exception ex)
  203.         {
  204.             s_Log.DebugFormat(
  205.                 "Exception in serial stream shutdown of port {0}: {1}", port.PortName, ex);
  206.         }
  207.  
  208.         try
  209.         {
  210.             s_Log.DebugFormat("Disposing serial port");
  211.             port.Close();
  212.         }
  213.         catch (Exception ex)
  214.         {
  215.             s_Log.DebugFormat("Exception in port {0} shutdown: {1}", port.PortName, ex);
  216.         }
  217.     }
  218.  
  219.     static void ShutdownEventLoopHandler(Stream internalSerialStream)
  220.     {
  221.         try
  222.         {
  223.             s_Log.DebugFormat("Working around .NET SerialPort class Dispose bug");
  224.  
  225.             FieldInfo eventRunnerField = internalSerialStream.GetType()
  226.                 .GetField("eventRunner", BindingFlags.NonPublic | BindingFlags.Instance);
  227.  
  228.             if (eventRunnerField == null)
  229.             {
  230.                 s_Log.WarnFormat(
  231.                     "Unable to find EventLoopRunner field. "
  232.                     + "SerialPort workaround failure. Application may crash after "
  233.                     + "disposing SerialPort unless .NET 1.1 unhandled exception "
  234.                     + "policy is enabled from the application's config file.");
  235.             }
  236.             else
  237.             {
  238.                 object eventRunner = eventRunnerField.GetValue(internalSerialStream);
  239.                 Type eventRunnerType = eventRunner.GetType();
  240.  
  241.                 FieldInfo endEventLoopFieldInfo = eventRunnerType.GetField(
  242.                     "endEventLoop", BindingFlags.Instance | BindingFlags.NonPublic);
  243.  
  244.                 FieldInfo eventLoopEndedSignalFieldInfo = eventRunnerType.GetField(
  245.                     "eventLoopEndedSignal", BindingFlags.Instance | BindingFlags.NonPublic);
  246.  
  247.                 FieldInfo waitCommEventWaitHandleFieldInfo = eventRunnerType.GetField(
  248.                     "waitCommEventWaitHandle", BindingFlags.Instance | BindingFlags.NonPublic);
  249.  
  250.                 if (endEventLoopFieldInfo == null
  251.                     || eventLoopEndedSignalFieldInfo == null
  252.                     || waitCommEventWaitHandleFieldInfo == null)
  253.                 {
  254.                     s_Log.WarnFormat(
  255.                         "Unable to find the EventLoopRunner internal wait handle or loop signal fields. "
  256.                         + "SerialPort workaround failure. Application may crash after "
  257.                         + "disposing SerialPort unless .NET 1.1 unhandled exception "
  258.                         + "policy is enabled from the application's config file.");
  259.                 }
  260.                 else
  261.                 {
  262.                     s_Log.DebugFormat(
  263.                         "Waiting for the SerialPort internal EventLoopRunner thread to finish...");
  264.  
  265.                     var eventLoopEndedWaitHandle =
  266.                         (WaitHandle)eventLoopEndedSignalFieldInfo.GetValue(eventRunner);
  267.                     var waitCommEventWaitHandle =
  268.                         (ManualResetEvent)waitCommEventWaitHandleFieldInfo.GetValue(eventRunner);
  269.  
  270.                     endEventLoopFieldInfo.SetValue(eventRunner, true);
  271.  
  272.                     // Sometimes the event loop handler resets the wait handle
  273.                     // before exiting the loop and hangs (in case of USB disconnect)
  274.                     // In case it takes too long, brute-force it out of its wait by
  275.                     // setting the handle again.
  276.                     do
  277.                     {
  278.                         waitCommEventWaitHandle.Set();
  279.                     } while (!eventLoopEndedWaitHandle.WaitOne(2000));
  280.  
  281.                     s_Log.DebugFormat("Wait completed. Now it is safe to continue disposal.");
  282.                 }
  283.             }
  284.         }
  285.         catch (Exception ex)
  286.         {
  287.             s_Log.ErrorFormat(
  288.                 "SerialPort workaround failure. Application may crash after "
  289.                 + "disposing SerialPort unless .NET 1.1 unhandled exception "
  290.                 + "policy is enabled from the application's config file: {0}",
  291.                 ex);
  292.         }
  293.     }
  294. }