/// <summary> /// Do work based on the mask event that has occurred. /// </summary> /// <param name="mask">The mask that was provided.</param> private void ProcessWaitCommEvent(NativeMethods.SerialEventMask mask) { if (mask != (int)0) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: ProcessWaitCommEvent: {1}", m_Name, mask); } // Reading a character if ((mask & NativeMethods.SerialEventMask.EV_RXCHAR) != 0) { m_ReadByteAvailable = true; } if ((mask & NativeMethods.SerialEventMask.EV_RXFLAG) != 0) { m_ReadByteAvailable = true; m_ReadByteEof = true; } // We don't raise an event for characters immediately, but only after the read operation // is complete. OnCommEvent(new CommEventArgs(mask & ~(NativeMethods.SerialEventMask.EV_RXCHAR | NativeMethods.SerialEventMask.EV_RXFLAG))); if ((mask & NativeMethods.SerialEventMask.EV_TXEMPTY) != 0) { lock (m_Buffer.WriteLock) { if (m_Buffer.Serial.TxEmptyEvent()) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: ProcessWaitCommEvent: TX-BUFFER empty", m_Name); } } } if ((mask & (NativeMethods.SerialEventMask.EV_RXCHAR | NativeMethods.SerialEventMask.EV_ERR)) != 0) { NativeMethods.ComStatErrors comErr; bool result = UnsafeNativeMethods.ClearCommError(m_ComPortHandle, out comErr, IntPtr.Zero); if (!result) { int w32err = Marshal.GetLastWin32Error(); int hr = Marshal.GetHRForLastWin32Error(); SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: ClearCommError: WINERROR {1}", m_Name, w32err); Marshal.ThrowExceptionForHR(hr); } else { comErr = (NativeMethods.ComStatErrors)((int)comErr & 0x10F); if (comErr != 0) { OnCommErrorEvent(new CommErrorEventArgs(comErr)); } } } }
/// <summary> /// Check if we should execute WaitCommEvent() and get the result if immediately available. /// </summary> /// <remarks> /// This function abstracts the Win32 API WaitCommEvent(). It assumes overlapped I/O. /// Therefore, when calling this function, you should ensure that the parameter <c>mask</c> /// and <c>overlap</c> are pinned for the duration of the overlapped I/O. Any easy way to /// do this is to allocate the variables on the stack and then pass them by reference to /// this function. /// <para>You should not call this function if a pending I/O operation for WaitCommEvent() /// is still open. It is an error otherwise.</para> /// </remarks> /// <param name="mask">The mask value if information is available immediately.</param> /// <param name="overlap">The overlap structure to use.</param> /// <returns>If the operation is pending or not.</returns> private bool DoWaitCommEvent(out NativeMethods.SerialEventMask mask, ref NativeOverlapped overlap) { bool result = UnsafeNativeMethods.WaitCommEvent(m_ComPortHandle, out mask, ref overlap); if (!result) { int w32err = Marshal.GetLastWin32Error(); int hr = Marshal.GetHRForLastWin32Error(); if (w32err != WinError.ERROR_IO_PENDING) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Error, 0, "{0}: SerialThread: DoWaitCommEvent: Result: {1}", m_Name, w32err); throw new System.IO.IOException("WaitCommEvent overlapped exception", hr); } } else { ProcessWaitCommEvent(mask); } return(!result); }
public static extern bool WaitCommEvent(SafeFileHandle hFile, [MarshalAs(UnmanagedType.U4)] out NativeMethods.SerialEventMask lpEvtMask, ref System.Threading.NativeOverlapped lpOverlapped);
public static extern bool SetCommMask(SafeFileHandle hFile, [MarshalAs(UnmanagedType.U4)] NativeMethods.SerialEventMask dwEvtMask);
private void OverlappedIoThreadMainLoop() { // WaitCommEvent bool serialCommPending = false; bool serialCommError = false; m_SerialCommEvent.Reset(); NativeOverlapped serialCommOverlapped = new NativeOverlapped(); #if NETSTANDARD15 serialCommOverlapped.EventHandle = m_SerialCommEvent.GetSafeWaitHandle().DangerousGetHandle(); #else serialCommOverlapped.EventHandle = m_SerialCommEvent.SafeWaitHandle.DangerousGetHandle(); #endif // ReadFile bool readPending = false; m_ReadEvent.Reset(); NativeOverlapped readOverlapped = new NativeOverlapped(); #if NETSTANDARD15 readOverlapped.EventHandle = m_ReadEvent.GetSafeWaitHandle().DangerousGetHandle(); #else readOverlapped.EventHandle = m_ReadEvent.SafeWaitHandle.DangerousGetHandle(); #endif // WriteFile bool writePending = false; m_WriteEvent.Reset(); NativeOverlapped writeOverlapped = new NativeOverlapped(); m_ReadByteAvailable = false; #if NETSTANDARD15 writeOverlapped.EventHandle = m_WriteEvent.GetSafeWaitHandle().DangerousGetHandle(); #else writeOverlapped.EventHandle = m_WriteEvent.SafeWaitHandle.DangerousGetHandle(); #endif // SEt up the types of serial events we want to see. UnsafeNativeMethods.SetCommMask(m_ComPortHandle, maskRead); bool result; NativeMethods.SerialEventMask commEventMask = 0; bool running = true; uint bytes; List <WaitHandle> handles = new List <WaitHandle>(10); while (running) { handles.Clear(); handles.Add(m_StopRunning); handles.Add(m_WriteClearEvent); #if PL2303_WORKAROUNDS // - - - - - - - - - - - - - - - - - - - - - - - - - // PROLIFIC PL23030 WORKAROUND // - - - - - - - - - - - - - - - - - - - - - - - - - // If we have a read pending, we don't request events // for reading data. To do so will result in errors. // Have no idea why. if (readPending) { UnsafeNativeMethods.SetCommMask(m_ComPortHandle, maskReadPending); } else { UnsafeNativeMethods.SetCommMask(m_ComPortHandle, maskRead); // While the comm event mask was set to ignore read events, data could have been written // to the input queue. Check for that and if there are bytes waiting or EOF was received, // set the appropriate flags. uint bytesInQueue; bool eofReceived; if (GetReceiveStats(out bytesInQueue, out eofReceived) && (bytesInQueue > 0 || eofReceived)) { // Tell DoReadEvent that there is data pending m_ReadByteAvailable = true; m_ReadByteEof |= eofReceived; } } #else UnsafeNativeMethods.SetCommMask(m_ComPortHandle, maskRead); #endif // commEventMask is on the stack, and is therefore fixed if (!serialCommError) { try { if (!serialCommPending) { serialCommPending = DoWaitCommEvent(out commEventMask, ref serialCommOverlapped); } if (serialCommPending) { handles.Add(m_SerialCommEvent); } } catch (System.IO.IOException) { // Some devices, such as the Arduino Uno with a CH340 on board don't support an overlapped // WaitCommEvent. So if that occurs, we remember it and don't use it again. The Windows error // returned was 87 (ERROR_INVALID_PARAMETER) was returned in that case. GetReceiveStats() did // work, so we can still know of data pending by polling. But we won't get any other events, // such as TX_EMPTY. SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Warning, 0, "{0}: SerialThread: Not processing WaitCommEvent events", m_Name); serialCommError = true; } } if (!readPending) { if (!m_Buffer.Serial.ReadBufferNotFull.WaitOne(0)) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: Read Buffer Full", m_Name); handles.Add(m_Buffer.Serial.ReadBufferNotFull); } else { readPending = DoReadEvent(ref readOverlapped); } } if (readPending) { handles.Add(m_ReadEvent); } if (!writePending) { if (!m_Buffer.Serial.WriteBufferNotEmpty.WaitOne(0)) { handles.Add(m_Buffer.Serial.WriteBufferNotEmpty); } else { writePending = DoWriteEvent(ref writeOverlapped); } } if (writePending) { handles.Add(m_WriteEvent); } // We wait up to 100ms, in case we're not actually pending on anything. Normally, we should always be // pending on a Comm event. Just in case this is not so (and is a theoretical possibility), we will // slip out of this WaitAny() after 100ms and then restart the loop, effectively polling every 100ms in // worst case. WaitHandle[] whandles = handles.ToArray(); int ev = WaitHandle.WaitAny(whandles, 100); if (ev != WaitHandle.WaitTimeout) { if (whandles[ev] == m_StopRunning) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: Thread closing", m_Name); result = UnsafeNativeMethods.CancelIo(m_ComPortHandle); if (!result) { int win32Error = Marshal.GetLastWin32Error(); int hr = Marshal.GetHRForLastWin32Error(); SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Warning, 0, "{0}: SerialThread: CancelIo error {1}", m_Name, win32Error); Marshal.ThrowExceptionForHR(hr); } running = false; } else if (whandles[ev] == m_SerialCommEvent) { result = UnsafeNativeMethods.GetOverlappedResult(m_ComPortHandle, ref serialCommOverlapped, out bytes, true); if (!result) { int win32Error = Marshal.GetLastWin32Error(); int hr = Marshal.GetHRForLastWin32Error(); SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Error, 0, "{0}: SerialThread: Overlapped WaitCommEvent() error {1}", m_Name, win32Error); Marshal.ThrowExceptionForHR(hr); } ProcessWaitCommEvent(commEventMask); serialCommPending = false; } else if (whandles[ev] == m_ReadEvent) { result = UnsafeNativeMethods.GetOverlappedResult(m_ComPortHandle, ref readOverlapped, out bytes, true); if (!result) { int win32Error = Marshal.GetLastWin32Error(); int hr = Marshal.GetHRForLastWin32Error(); // Should never get ERROR_IO_PENDING, as this method is only called when the event is triggered. if (win32Error != WinError.ERROR_OPERATION_ABORTED || bytes > 0) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Error, 0, "{0}: SerialThread: Overlapped ReadFile() error {1} bytes {2}", m_Name, win32Error, bytes); Marshal.ThrowExceptionForHR(hr); } else { // ERROR_OPERATION_ABORTED may be caused by CancelIo or PurgeComm if (SerialTrace.TraceSer.Switch.ShouldTrace(System.Diagnostics.TraceEventType.Verbose)) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: Overlapped ReadFile() error {1} bytes {2}", m_Name, win32Error, bytes); } } } else { ProcessReadEvent(bytes); } readPending = false; } else if (whandles[ev] == m_Buffer.Serial.ReadBufferNotFull) { // The read buffer is no longer full. We just loop back to the beginning to test if we // should read or not. } else if (whandles[ev] == m_WriteEvent) { result = UnsafeNativeMethods.GetOverlappedResult(m_ComPortHandle, ref writeOverlapped, out bytes, true); if (!result) { int win32Error = Marshal.GetLastWin32Error(); int hr = Marshal.GetHRForLastWin32Error(); // Should never get ERROR_IO_PENDING, as this method is only called when the event is triggered. if (win32Error != WinError.ERROR_OPERATION_ABORTED || bytes > 0) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Error, 0, "{0}: SerialThread: Overlapped WriteFile() error {1} bytes {2}", m_Name, win32Error, bytes); Marshal.ThrowExceptionForHR(hr); } else { // ERROR_OPERATION_ABORTED may be caused by CancelIo or PurgeComm if (SerialTrace.TraceSer.Switch.ShouldTrace(System.Diagnostics.TraceEventType.Verbose)) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: Overlapped WriteFile() error {1} bytes {2}", m_Name, win32Error, bytes); } } } else { ProcessWriteEvent(bytes); } writePending = false; } else if (whandles[ev] == m_Buffer.Serial.WriteBufferNotEmpty) { // The write buffer is no longer empty. We just loop back to the beginning to test if we // should write or not. } else if (whandles[ev] == m_WriteClearEvent) { if (writePending) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: PurgeComm() write pending", m_Name); m_PurgePending = true; result = UnsafeNativeMethods.PurgeComm(m_ComPortHandle, NativeMethods.PurgeFlags.PURGE_TXABORT | NativeMethods.PurgeFlags.PURGE_TXCLEAR); if (!result) { int win32Error = Marshal.GetLastWin32Error(); int hr = Marshal.GetHRForLastWin32Error(); if (win32Error != WinError.ERROR_OPERATION_ABORTED) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Error, 0, "{0}: SerialThread: PurgeComm() error {1}", m_Name, win32Error); Marshal.ThrowExceptionForHR(hr); } else { if (SerialTrace.TraceSer.Switch.ShouldTrace(System.Diagnostics.TraceEventType.Verbose)) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: PurgeComm() error {1}", m_Name, win32Error); } } } } else { lock (m_Buffer.WriteLock) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: Purged", m_Name); m_Buffer.Serial.Purge(); m_WriteClearDoneEvent.Set(); } } } } #if STRESSTEST SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: STRESSTEST SerialThread: Stress Test Delay of 1000ms", m_Name); System.Threading.Thread.Sleep(1000); NativeMethods.ComStatErrors commStateErrors = new NativeMethods.ComStatErrors(); NativeMethods.COMSTAT commStat = new NativeMethods.COMSTAT(); result = UnsafeNativeMethods.ClearCommError(m_ComPortHandle, out commStateErrors, out commStat); if (result) { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Information, 0, "{0}: STRESSTEST SerialThread: ClearCommError errors={1}", m_Name, commStateErrors); SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Information, 0, "{0}: STRESSTEST SerialThread: ClearCommError stats flags={1}, InQueue={2}, OutQueue={3}", m_Name, commStat.Flags, commStat.cbInQue, commStat.cbOutQue); } else { SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Warning, 0, "{0}: STRESSTEST SerialThread: ClearCommError error: {1}", m_Name, Marshal.GetLastWin32Error()); } #endif } }
private void SerialPortIo_CommEvent(object sender, NativeSerialPort.CommOverlappedIo.CommEventArgs e) { if (IsDisposed || DataReceived == null && PinChanged == null) { m_CommEvent = 0; return; } else { lock (m_EventCheck) { m_CommEvent |= e.EventType; } CallEvent(); } }
/// <summary> /// Raise events to the calling application of this class /// </summary> /// <remarks> /// This function should be executed from a ThreadPool thread. This method will check /// flags, call user events. It will repeat until no events remain pending. /// </remarks> /// <param name="state">Not used</param> private void HandleEvent(object state) { NativeMethods.SerialEventMask commEvent; NativeMethods.ComStatErrors commErrorEvent; bool handleEvent; lock (m_EventCheck) { handleEvent = (m_CommEvent != 0) || (m_CommErrorEvent != 0); commEvent = m_CommEvent; m_CommEvent = 0; commErrorEvent = m_CommErrorEvent; m_CommErrorEvent = 0; } while (handleEvent) { // Received Data if ((commEvent & NativeMethods.SerialEventMask.EV_RXFLAG) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnDataReceived(new SerialDataReceivedEventArgs(SerialData.Eof)); } else if ((commEvent & NativeMethods.SerialEventMask.EV_RXCHAR) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } if (m_SerialPort.SerialPortIo.BytesToRead >= m_RxThreshold) { OnDataReceived(new SerialDataReceivedEventArgs(SerialData.Chars)); } } // Modem Pin States if ((commEvent & NativeMethods.SerialEventMask.EV_CTS) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnPinChanged(new SerialPinChangedEventArgs(SerialPinChange.CtsChanged)); } if ((commEvent & NativeMethods.SerialEventMask.EV_RING) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnPinChanged(new SerialPinChangedEventArgs(SerialPinChange.Ring)); } if ((commEvent & NativeMethods.SerialEventMask.EV_RLSD) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnPinChanged(new SerialPinChangedEventArgs(SerialPinChange.CDChanged)); } if ((commEvent & NativeMethods.SerialEventMask.EV_DSR) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnPinChanged(new SerialPinChangedEventArgs(SerialPinChange.DsrChanged)); } if ((commEvent & NativeMethods.SerialEventMask.EV_BREAK) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnPinChanged(new SerialPinChangedEventArgs(SerialPinChange.Break)); } // Error States if ((commErrorEvent & NativeMethods.ComStatErrors.CE_TXFULL) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnCommError(new SerialErrorReceivedEventArgs(SerialError.TXFull)); } if ((commErrorEvent & NativeMethods.ComStatErrors.CE_FRAME) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnCommError(new SerialErrorReceivedEventArgs(SerialError.Frame)); } if ((commErrorEvent & NativeMethods.ComStatErrors.CE_RXPARITY) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnCommError(new SerialErrorReceivedEventArgs(SerialError.RXParity)); } if ((commErrorEvent & NativeMethods.ComStatErrors.CE_OVERRUN) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnCommError(new SerialErrorReceivedEventArgs(SerialError.Overrun)); } if ((commErrorEvent & NativeMethods.ComStatErrors.CE_RXOVER) != 0) { lock (m_EventCheck) { if (IsDisposed) { handleEvent = false; break; } } OnCommError(new SerialErrorReceivedEventArgs(SerialError.RXOver)); } lock (m_EventCheck) { handleEvent = (m_CommEvent != 0) || (m_CommErrorEvent != 0); commEvent = m_CommEvent; m_CommEvent = 0; commErrorEvent = m_CommErrorEvent; m_CommErrorEvent = 0; } } m_EventProcessing.Reset(); }
public static extern bool WaitCommEvent(SafeFileHandle hFile, out NativeMethods.SerialEventMask lpEvtMask, ref System.Threading.NativeOverlapped lpOverlapped);
/// <summary> /// Constructor. /// </summary> /// <param name="eventType">The event results.</param> public CommEventArgs(NativeMethods.SerialEventMask eventType) { m_EventType = eventType; }