Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
serialportio.cs
00001 using System; 00002 using System.Collections.Generic; 00003 using System.Linq; 00004 using System.Text; 00005 using System.Threading.Tasks; 00006 using System.Threading; 00007 using System.Reflection; 00008 using System.IO; 00009 00010 using System.IO.Ports; 00011 using SerialPortTester; 00012 00013 using Common.Logging; 00014 00015 namespace SerialWrap 00016 { 00017 public interface ISerialPortIo : IDisposable 00018 { 00019 string PortName { get; } 00020 string ReadLine(); 00021 void WriteLine(string text); 00022 } 00023 00024 public class SerialPortConfig 00025 { 00026 public string Name { get; private set; } 00027 public int BaudRate { get; private set; } 00028 public int DataBits { get; private set; } 00029 public StopBits StopBits { get; private set; } 00030 public Parity Parity { get; private set; } 00031 public bool DtrEnable { get; private set; } 00032 public bool RtsEnable { get; private set; } 00033 00034 public SerialPortConfig( 00035 string name, 00036 int baudRate, 00037 int dataBits, 00038 StopBits stopBits, 00039 Parity parity, 00040 bool dtrEnable, 00041 bool rtsEnable) 00042 { 00043 if (String.IsNullOrWhiteSpace(name)) throw new ArgumentNullException("name"); 00044 00045 this.RtsEnable = rtsEnable; 00046 this.BaudRate = baudRate; 00047 this.DataBits = dataBits; 00048 this.StopBits = stopBits; 00049 this.Parity = parity; 00050 this.DtrEnable = dtrEnable; 00051 this.Name = name; 00052 } 00053 00054 public override string ToString() 00055 { 00056 return String.Format( 00057 "{0} (Baud: {1}/DataBits: {2}/Parity: {3}/StopBits: {4}/{5})", 00058 this.Name, 00059 this.BaudRate, 00060 this.DataBits, 00061 this.Parity, 00062 this.StopBits, 00063 this.RtsEnable ? "RTS" : "No RTS"); 00064 } 00065 } 00066 00067 // Wrapper around SerialPort 00068 public class SerialPortIo : ISerialPortIo 00069 { 00070 protected ILog Log { get; private set; } 00071 static readonly ILog s_Log = LogManager.GetLogger(typeof(SerialPortIo)); 00072 00073 readonly SerialPort _port; 00074 readonly Stream _internalSerialStream; 00075 00076 private int readTimeout; 00077 private int writeTimeout; 00078 00079 public int ReadTimeout 00080 { 00081 get { return readTimeout; } 00082 set 00083 { 00084 readTimeout = value; 00085 _port.ReadTimeout = readTimeout; 00086 } 00087 } 00088 00089 public int WriteTimeout 00090 { 00091 get { return writeTimeout; } 00092 set 00093 { 00094 writeTimeout = value; 00095 _port.WriteTimeout = writeTimeout; 00096 } 00097 } 00098 00099 public void DiscardInBuffer() 00100 { 00101 _port.DiscardInBuffer(); 00102 } 00103 00104 public bool IsOpen 00105 { 00106 get { return _port.IsOpen; } 00107 } 00108 00109 public SerialPortIo(SerialPortConfig portConfig) 00110 { 00111 if (portConfig == null) throw new ArgumentNullException("portConfig"); 00112 00113 this.Log = LogManager.GetLogger(this.GetType()); 00114 00115 // http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html 00116 SerialPortFixer.Execute(portConfig.Name); 00117 00118 var port = new SerialPort( 00119 portConfig.Name, 00120 portConfig.BaudRate, 00121 portConfig.Parity, 00122 portConfig.DataBits, 00123 portConfig.StopBits) 00124 { 00125 RtsEnable = portConfig.RtsEnable, 00126 DtrEnable = portConfig.DtrEnable, 00127 ReadTimeout = 5000, 00128 WriteTimeout = 5000 00129 }; 00130 port.Open(); 00131 00132 try 00133 { 00134 this._internalSerialStream = port.BaseStream; 00135 this._port = port; 00136 this._port.DiscardInBuffer(); 00137 this._port.DiscardOutBuffer(); 00138 } 00139 catch (Exception ex) 00140 { 00141 Stream internalStream = this._internalSerialStream; 00142 00143 if (internalStream == null) 00144 { 00145 FieldInfo field = typeof(SerialPort).GetField( 00146 "internalSerialStream", 00147 BindingFlags.Instance | BindingFlags.NonPublic); 00148 00149 // This will happen if the SerialPort class is changed 00150 // in future versions of the .NET Framework 00151 if (field == null) 00152 { 00153 this.Log.WarnFormat( 00154 "An exception occured while creating the serial port adaptor, " 00155 + "the internal stream reference was not acquired and we were unable " 00156 + "to get it using reflection. The serial port may not be accessible " 00157 + "any further until the serial port object finalizer has been run: {0}", 00158 ex); 00159 00160 throw; 00161 } 00162 00163 internalStream = (Stream)field.GetValue(port); 00164 } 00165 00166 this.Log.DebugFormat( 00167 "An error occurred while constructing the serial port adaptor: {0}", ex); 00168 00169 SafeDisconnect(port, internalStream); 00170 throw; 00171 } 00172 } 00173 00174 public string PortName 00175 { 00176 get { return this._port.PortName; } 00177 } 00178 00179 public int Read(char[] buffer, int offset, int count) 00180 { 00181 return this._port.Read(buffer, offset, count); 00182 } 00183 00184 public int Read(byte[] buffer, int offset, int count) 00185 { 00186 return this._port.Read(buffer, offset, count); 00187 } 00188 00189 public string ReadLine() 00190 { 00191 //return this._port.ReadTo(Environment.NewLine); 00192 return this._port.ReadLine(); 00193 } 00194 00195 public string ReadExisting() 00196 { 00197 return this._port.ReadExisting(); 00198 } 00199 00200 public void Write(string text) 00201 { 00202 this._port.Write(text); 00203 } 00204 00205 public void Write(char[] buffer, int offset, int count) 00206 { 00207 this._port.Write(buffer, offset, count); 00208 } 00209 00210 00211 public void WriteLine(string text) 00212 { 00213 //this._port.Write(text); 00214 //this._port.Write("\r"); 00215 this._port.WriteLine(text); 00216 } 00217 00218 public void Dispose() 00219 { 00220 this.Dispose(true); 00221 } 00222 00223 protected void Dispose(bool disposing) 00224 { 00225 SafeDisconnect(this._port, this._internalSerialStream); 00226 00227 if (disposing) 00228 { 00229 GC.SuppressFinalize(this); 00230 } 00231 } 00232 00233 /// <summary> 00234 /// Safely closes a serial port and its internal stream even if 00235 /// a USB serial interface was physically removed from the system 00236 /// in a reliable manner. 00237 /// </summary> 00238 /// <param name="port"></param> 00239 /// <param name="internalSerialStream"></param> 00240 /// <remarks> 00241 /// The <see cref="SerialPort"/> class has 3 different problems in disposal 00242 /// in case of a USB serial device that is physically removed: 00243 /// 00244 /// 1. The eventLoopRunner is asked to stop and <see cref="SerialPort.IsOpen"/> 00245 /// returns false. Upon disposal this property is checked and closing 00246 /// the internal serial stream is skipped, thus keeping the original 00247 /// handle open indefinitely (until the finalizer runs which leads to the next problem) 00248 /// 00249 /// The solution for this one is to manually close the internal serial stream. 00250 /// We can get its reference by <see cref="SerialPort.BaseStream" /> 00251 /// before the exception has happened or by reflection and getting the 00252 /// "internalSerialStream" field. 00253 /// 00254 /// 2. Closing the internal serial stream throws an exception and closes 00255 /// the internal handle without waiting for its eventLoopRunner thread to finish, 00256 /// causing an uncatchable ObjectDisposedException from it later on when the finalizer 00257 /// runs (which oddly avoids throwing the exception but still fails to wait for 00258 /// the eventLoopRunner). 00259 /// 00260 /// The solution is to manually ask the event loop runner thread to shutdown 00261 /// (via reflection) and waiting for it before closing the internal serial stream. 00262 /// 00263 /// 3. Since Dispose throws exceptions, the finalizer is not suppressed. 00264 /// 00265 /// The solution is to suppress their finalizers at the beginning. 00266 /// </remarks> 00267 static void SafeDisconnect(SerialPort port, Stream internalSerialStream) 00268 { 00269 GC.SuppressFinalize(port); 00270 GC.SuppressFinalize(internalSerialStream); 00271 00272 ShutdownEventLoopHandler(internalSerialStream); 00273 00274 try 00275 { 00276 s_Log.DebugFormat("Disposing internal serial stream"); 00277 internalSerialStream.Close(); 00278 } 00279 catch (Exception ex) 00280 { 00281 s_Log.DebugFormat( 00282 "Exception in serial stream shutdown of port {0}: {1}", port.PortName, ex); 00283 } 00284 00285 try 00286 { 00287 s_Log.DebugFormat("Disposing serial port"); 00288 port.Close(); 00289 } 00290 catch (Exception ex) 00291 { 00292 s_Log.DebugFormat("Exception in port {0} shutdown: {1}", port.PortName, ex); 00293 } 00294 } 00295 00296 static void ShutdownEventLoopHandler(Stream internalSerialStream) 00297 { 00298 try 00299 { 00300 s_Log.DebugFormat("Working around .NET SerialPort class Dispose bug"); 00301 00302 FieldInfo eventRunnerField = internalSerialStream.GetType() 00303 .GetField("eventRunner", BindingFlags.NonPublic | BindingFlags.Instance); 00304 00305 if (eventRunnerField == null) 00306 { 00307 s_Log.WarnFormat( 00308 "Unable to find EventLoopRunner field. " 00309 + "SerialPort workaround failure. Application may crash after " 00310 + "disposing SerialPort unless .NET 1.1 unhandled exception " 00311 + "policy is enabled from the application's config file."); 00312 } 00313 else 00314 { 00315 object eventRunner = eventRunnerField.GetValue(internalSerialStream); 00316 Type eventRunnerType = eventRunner.GetType(); 00317 00318 FieldInfo endEventLoopFieldInfo = eventRunnerType.GetField( 00319 "endEventLoop", BindingFlags.Instance | BindingFlags.NonPublic); 00320 00321 FieldInfo eventLoopEndedSignalFieldInfo = eventRunnerType.GetField( 00322 "eventLoopEndedSignal", BindingFlags.Instance | BindingFlags.NonPublic); 00323 00324 FieldInfo waitCommEventWaitHandleFieldInfo = eventRunnerType.GetField( 00325 "waitCommEventWaitHandle", BindingFlags.Instance | BindingFlags.NonPublic); 00326 00327 if (endEventLoopFieldInfo == null 00328 || eventLoopEndedSignalFieldInfo == null 00329 || waitCommEventWaitHandleFieldInfo == null) 00330 { 00331 s_Log.WarnFormat( 00332 "Unable to find the EventLoopRunner internal wait handle or loop signal fields. " 00333 + "SerialPort workaround failure. Application may crash after " 00334 + "disposing SerialPort unless .NET 1.1 unhandled exception " 00335 + "policy is enabled from the application's config file."); 00336 } 00337 else 00338 { 00339 s_Log.DebugFormat( 00340 "Waiting for the SerialPort internal EventLoopRunner thread to finish..."); 00341 00342 var eventLoopEndedWaitHandle = 00343 (WaitHandle)eventLoopEndedSignalFieldInfo.GetValue(eventRunner); 00344 var waitCommEventWaitHandle = 00345 (ManualResetEvent)waitCommEventWaitHandleFieldInfo.GetValue(eventRunner); 00346 00347 endEventLoopFieldInfo.SetValue(eventRunner, true); 00348 00349 // Sometimes the event loop handler resets the wait handle 00350 // before exiting the loop and hangs (in case of USB disconnect) 00351 // In case it takes too long, brute-force it out of its wait by 00352 // setting the handle again. 00353 do 00354 { 00355 waitCommEventWaitHandle.Set(); 00356 } while (!eventLoopEndedWaitHandle.WaitOne(2000)); 00357 00358 s_Log.DebugFormat("Wait completed. Now it is safe to continue disposal."); 00359 } 00360 } 00361 } 00362 catch (Exception ex) 00363 { 00364 s_Log.ErrorFormat( 00365 "SerialPort workaround failure. Application may crash after " 00366 + "disposing SerialPort unless .NET 1.1 unhandled exception " 00367 + "policy is enabled from the application's config file: {0}", 00368 ex); 00369 } 00370 } 00371 } 00372 }
Generated on Tue Jul 12 2022 21:52:40 by
1.7.2