Tuesday, July 6, 2010

SerialPort IOException Workaround in C#

As promised, I've whipped up a quick workaround to fix the problem as described here.

Here's the code:

// Copyright 2010-2014 Zach Saw // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System; using System.IO; using System.IO.Ports; using System.Runtime.InteropServices; using System.Text; using Microsoft.Win32.SafeHandles; namespace SerialPortTester { public class SerialPortFixer : IDisposable { public static void Execute(string portName) { using (new SerialPortFixer(portName)) { } } #region IDisposable Members public void Dispose() { if (m_Handle != null) { m_Handle.Close(); m_Handle = null; } } #endregion #region Implementation private const int DcbFlagAbortOnError = 14; private const int CommStateRetries = 10; private SafeFileHandle m_Handle; private SerialPortFixer(string portName) { const int dwFlagsAndAttributes = 0x40000000; const int dwAccess = unchecked((int) 0xC0000000); if ((portName == null) || !portName.StartsWith("COM", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("Invalid Serial Port", "portName"); } SafeFileHandle hFile = CreateFile(@"\\.\" + portName, dwAccess, 0, IntPtr.Zero, 3, dwFlagsAndAttributes, IntPtr.Zero); if (hFile.IsInvalid) { WinIoError(); } try { int fileType = GetFileType(hFile); if ((fileType != 2) && (fileType != 0)) { throw new ArgumentException("Invalid Serial Port", "portName"); } m_Handle = hFile; InitializeDcb(); } catch { hFile.Close(); m_Handle = null; throw; } } [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int FormatMessage(int dwFlags, HandleRef lpSource, int dwMessageId, int dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr arguments); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool GetCommState(SafeFileHandle hFile, ref Dcb lpDcb); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool SetCommState(SafeFileHandle hFile, ref Dcb lpDcb); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool ClearCommError(SafeFileHandle hFile, ref int lpErrors, ref Comstat lpStat); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr securityAttrs, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32.dll", SetLastError = true)] private static extern int GetFileType(SafeFileHandle hFile); private void InitializeDcb() { Dcb dcb = new Dcb(); GetCommStateNative(ref dcb); dcb.Flags &= ~(1u << DcbFlagAbortOnError); SetCommStateNative(ref dcb); } private static string GetMessage(int errorCode) { StringBuilder lpBuffer = new StringBuilder(0x200); if ( FormatMessage(0x3200, new HandleRef(null, IntPtr.Zero), errorCode, 0, lpBuffer, lpBuffer.Capacity, IntPtr.Zero) != 0) { return lpBuffer.ToString(); } return "Unknown Error"; } private static int MakeHrFromErrorCode(int errorCode) { return (int) (0x80070000 | (uint) errorCode); } private static void WinIoError() { int errorCode = Marshal.GetLastWin32Error(); throw new IOException(GetMessage(errorCode), MakeHrFromErrorCode(errorCode)); } private void GetCommStateNative(ref Dcb lpDcb) { int commErrors = 0; Comstat comStat = new Comstat(); for (int i = 0; i < CommStateRetries; i++) { if (!ClearCommError(m_Handle, ref commErrors, ref comStat)) { WinIoError(); } if (GetCommState(m_Handle, ref lpDcb)) { break; } if (i == CommStateRetries - 1) { WinIoError(); } } } private void SetCommStateNative(ref Dcb lpDcb) { int commErrors = 0; Comstat comStat = new Comstat(); for (int i = 0; i < CommStateRetries; i++) { if (!ClearCommError(m_Handle, ref commErrors, ref comStat)) { WinIoError(); } if (SetCommState(m_Handle, ref lpDcb)) { break; } if (i == CommStateRetries - 1) { WinIoError(); } } } #region Nested type: COMSTAT [StructLayout(LayoutKind.Sequential)] private struct Comstat { public readonly uint Flags; public readonly uint cbInQue; public readonly uint cbOutQue; } #endregion #region Nested type: DCB [StructLayout(LayoutKind.Sequential)] private struct Dcb { public readonly uint DCBlength; public readonly uint BaudRate; public uint Flags; public readonly ushort wReserved; public readonly ushort XonLim; public readonly ushort XoffLim; public readonly byte ByteSize; public readonly byte Parity; public readonly byte StopBits; public readonly byte XonChar; public readonly byte XoffChar; public readonly byte ErrorChar; public readonly byte EofChar; public readonly byte EvtChar; public readonly ushort wReserved1; } #endregion #endregion } internal class Program { private static void Main(string[] args) { SerialPortFixer.Execute("COM1"); using (SerialPort port = new SerialPort("COM1")) { port.Write("test"); } } } }


* Use Firefox to copy and paste the code above. Formatting won't be preserved if you use IE (IE bug).

52 comments:

Ravenheart said...

Absolutely awesome, saved me a TON of headaches. Thank you!

Anonymous said...

Wow, you are the man!!
I've wouldn't have guessed to clear that flag...

Grungean said...

Zach, is this code for .NET 2.0 or .NET 3.5?
Thanks
Grungean

Grungean said...

Hi Zac, will this code with .NET 3.5 or only with .NET 2.0?

Zach Saw said...

@Grungean: It's compatible with .NET 2.0 and above.

Dan du Preez said...

I am so glad someone has worked out what's going on here! But I am having a very strange issue when I try and apply the workaround.

The SerialPortFixer (your code) works fine, it gets a good filehandle and everything, but when I run my SerialPort.Open() I get an UnauthorisedAccessException thrown. Any idea what I'm doing wrong?

I call your .execute(port) method and then immediately call my code to open the port, so it's very similar to your sample.

Mateusz said...

One more thing. There is one small problem about this matter in Compact Framework. The IOException does not propagate the Error number, so getting this Exception I was not able to look for it in google...

Sean Riddle said...

I found this post when searching about a different crash I was getting using SerialPort. I'd heard about this issue before, so I converted your code to C++ and added it to my project. Strangely enough, it seems to have fixed my original issue.
I run several serial port data loggers on an XP machine. Some or all of the data loggers would crash about 1/4 of the time that I logged into that PC with remote desktop. I'd get a mystery exception that I could not catch. I never found any info appropriate to that issue, but I think your workaround fixed it.

Anonymous said...

How would I put this code into my project?

Zach Saw said...

@Anonymous

As per usual.

Daniel said...

Thanks Zach, I've been struggling with this issue for ages!

Anonymous said...

Hey Zach, you saved my life and a lot of time ;)
Best regards
PI

Richard Collette said...

I wonder if there is an easy way to add the fix call into code prior to each constructor call using Mono.Cecil and a custom build task. Would be a nice way to not have to write a decorator and pull the code in the future when if/when it is no longer necessary.

ICAO H-IWG Technical Group said...

I'm having the same issue that Dan du Preez mentions. I'm getting access is denied error because the CreateFile line is returning an IsInvalid as true. Any ideas?

I'm trying to use this code to clean up serial ports. I've got 4 different devices enumerating serial ports and I'm selecting which device is paired with which serial port, so they all have to clean up the serial port before they start using them.

Zach Saw said...

@ICAO:

Without the fix, did you have the same problem? If so, it's most likely a separate issue - perhaps hardware?

Anonymous said...

Great post. I have a simple app that crash inconsistently when serial port is close. I tried implementing your codes in the sample. Add the SerialPortFixer.Execute("COM1"); However the same I/O exception still persist. I suspect I'm implementing your code wrong? All I did is copy and paste the whole thing and add ther SerialPortFixer.Execute before SerialPort.Open.

Zach Saw said...

Not all IOExceptions are related to the problem I posted. some could genuinely be driver bugs or your code bug.

Anonymous said...

Many thanks, this has saved us a massive headache!

dave said...

Hello, and thank you. I've implemented the class as you've written it in the post, and my application seems to be a lot more stable as a result. I am still having an odd problem closing the serial ports though. My application handles printing from several POS printers connected to multiple com port card(s) to print small tickets for a raffle system and each printer can take 6 or more seconds to close. We have a customer with 14 printers connected and can take as long as 10 minutes for all ports to close on another thread. I can post the closing code if you'd like, but does this sound like it should be expected? Normally I'd keep banging away at this, but I have to admit I'm stumped.

dave said...

Sorry, nevermind, my mistake, I'm an idiot.

Ferdinand said...

I am using .NET 2.0 and still getting the ObjectDisposeException (Safe handle has bee closed) about 3 seconds after unplugging the USB device.
Now I probably implement it in C/C++.

antiduh said...

The code is overzealous on checking the name of the serial port:

if ( (portName == null) || !portName.StartsWith( "COM", StringComparison.OrdinalIgnoreCase ) )
{
throw new ArgumentException( "Invalid Serial Port", "portName" );
}

Serial ports don't have to be named COMxx, though that is the convention that most vendors use. For instance, I have software that implements a null-pipe between two 'com' ports (com0com) so that you can test software without having physical com ports. That software names the ports whatever you want.

Zach Saw said...

@antiduh

Duh (couldn't resist)! Tell that to .NET framework team, would ya? That's straight from them! What's the point of allowing COM port names other than the ones .NET framework accepts?

Anonymous said...

can this be implemented in vb.net?

Anonymous said...

Zach,
I too am having the same issue as ICAO and Dan du Preez. This error does NOT occur if the fix is not included.
I have two ports on my computer. one port is used by HyperTerminal (connected, COM1) and another port (COM2) is available. I tried to launch my application but it crashes at this line

" throw new IOException(GetMessage(errorCode), MakeHrFromErrorCode(errorCode));" in function WinIoError()

Says IOException was unhandled - Access denied.

If I do not include the fix, then my app launches fine. I can connect to the available port (COM2) just fine.

Please help... Thank you so much!

A Engineer said...

Thanks, we've been dealing with this issue for about two weeks now. Crazy that the solution is so simple...

Anonymous said...

Thank you my computer is giving the exception even on single write() on the beginning of program. THX..

Anonymous said...

Thanks a lot - you made me happy :-)

Anonymous said...

Thank you very much..you saved me a lot of headache and pain stay blessed.

Griever said...

The same problem as as ICAO & Dan du Preez.
Waiting for fix.

Foster said...

I am trying to figure out a few things.

I am currently using the SerialPort Class in my program (it works but I get framing errors) because I use mark and space parity
First byte from the PC must be Mark parity (9th bit logic 1)
Rest of the bytes must be Space parity and receive is always space parity.

Can I change parity on the fly?
How do I set or modify the parameters setting in my code?

Also I can not send character data must be sent as bytes or byte array if possible?

Anonymous said...

I used this fix, and I still had problems. So I continue searching for a solution, and I found one: when you are going to open the port, use the following code:

GC.SuppressFinalize(port); port.Open(); GC.SuppressFinalize(port.BaseStream);

And, when you close your application or you are going to close the port, use the following code:

GC.ReRegisterForFinalize(port.BaseStream);
port.Close(); GC.ReRegisterForFinalize(port);

For me, this has solved the problem. Hope this helps more people.

samtal said...

Thanks smart Zach. (and others, smart too...).
It is really annoying that MS let us all waste our time on their neglect...
I have an application with com port that works well for over a year, both read and write.
I added now one small function intended to read back one acknowledge byte from the other serial side.
For that I open the port and use Myport.ReadByte().
I can see the byte is present at the port (as seen in debug), but in normal run it always crashes with "The I/O operation has been aborted because of either a thread exit or an application request."
When running in debug, more often than not it runs OK.
Have tried delays and waiting loops, but for no avail.
As it looks like this forum thread is just what I need, I tried to add Zach's class, but that class has many of the port properties I already have in my port class, and I am not sure of what the bare minimum I need to leave in Zach's class to test it.
(I naturally moved the call to the class to my program).
Please, advice.
samtal

HorstJ said...

Thanks to everyone fighting against this problem. I tried the serialportfixer and the GC.SuppressFinalize suggestion, but unfortunately none of them fixed my problem. I still get the exception: "System.IO.IOException: Ein an das System angeschlossenes Gerät funktioniert nicht.

at System.IO.Ports.InternalResources.WinIOError(Int32 errorCode, String str)
at System.IO.Ports.InternalResources.WinIOError()
at System.IO.Ports.SerialStream.set_DtrEnable(Boolean value)
at System.IO.Ports.SerialStream..ctor(String portName, Int32 baudRate, Parity parity, Int32 dataBits, StopBits stopBits, Int32 readTimeout, Int32 writeTimeout, Handshake handshake, Boolean dtrEnable, Boolean rtsEnable, Boolean discardNull, Byte parityReplace)
at System.IO.Ports.SerialPort.Open()
at Helice.Helice._RequestHeliceInfo(String port) in D:\devicemanager\DeviceManager\DeviceManager\Helice\Helice.cs:line 400" almost every time I try to open the port :-( Does anyone has anymore idea how to solve this?

Ken Wilson said...

Zach. Nice job on this. The GC.SuppressFinalize suggestion works in preventing the IO error but causes a worse problem. I have an application running on tablets (Windows 7, .NET Framework 4.) that is GPS enabled. Problem is that users have a tendency to unplug the GPS dongle from the USB port. As soon as they do that, we get the IO error. But using your code and the GC.SuppressFinalize idea, I avoid the IO error, but then basic windows control functions, like form closing, don't work properly (events get fired but form doesn't close). If I remove the GC.SuppressFinalize from my code, the form works just fine (except for the IO error when the user unplugs the GPS device). I'm in a bit of a vicious loop here. Any suggestions?

Francois said...

Absolutely perfect, converted to VB and work perfect !!

Thank You!

Anonymous said...

@ Sean Riddle: Can u plz share your c++ code

eejake52 said...

I put in a debug statement after GetCommState. This helped me to determine who the culprit was (HyperTerm). e.g.

GetCommStateNative(ref dcb);
uint fAbort = dcb.Flags & (1u << DcbFlagAbortOnError);
Debug.WriteLine("fAbort as found = " + fAbort.ToString());

OurTie said...

@Ken Wilson: I have the same situation with a GPS USB where the users will unplug the device Ken. Have you (or anyone else) been able to find a work around to this situation? It is really a critical issue for us and IS DRIVING ME CRAZY!!! Thank you Microsoft >:(

Damian Williams said...

I pasted code into new module, but have no idea how to referance it?

I get this i/o thread issue when my usb voice modem is tet as AT-STR=3 trying to detect when the extention goes off hook...

i get 4-5 messages coming in from the serial port, but none of them can be read because of the error...

any advice please email me: ctf_dai_lafing@msn.com

OurTie said...

Got rid of all my headaches by just writing a simple managed C++ dll and having my C# project reference that instead of the .NET SerialPort class.

Thanks SO MUCH M$!

Rich said...

Great code. I have been using it successfully as 32 bit for some time. Just switched the application to 64 bit and on 64 bit machine it breaks. GetFileType throws an Access is denied error? Any ideas anyone?

IanS said...

What's the reasoning behind CommStateRetries=10 ?

Is it really necessary to try it 10 times ?

Also, it it necessary to call ClearCommErrors before calling Get/SetComState ?

Thanks
Ian

Trevor Woods said...
This comment has been removed by the author.
Anonymous said...

I still have same problem i.e "A device attached to the System is not functioning"

Jeff Frandsen said...

Thanks so much for posting this fix - I have been struggling with this problem for years.

I'm VERY grateful!

Jeff

Marty Webster said...

The gift that keeps on giving. Just found your blog today and it works great. Thanks very much for the post.

Anonymous said...

Thanks a bunch! Searched for an hour before I found this workaround.

My code had been working for a long time, but suddenly one day I started getting this problem on the .Open() method.

GremlinSA said...

Zach ... thanks so much for your well put article and code snip....

I just suddenly started having problems with a service not been able to connect to Serial ports..

I implemented this code (granted in VB.NET) and it now works ...

BTW, I put up the VB code here -> http://forums.codeguru.com/showthread.php?537211-Com-Ports-in-a-Service

thanks...

Anonymous said...

I've pasted the snippet, believing that this would finally work... It seemed so obvious it will. But after running the code, it fails on SetCommState call. As I see, this function is called for 10 times, and if the result is still not good, it throws an exception.
But even trying to omit this (not throwing the exception), still opening of the port is impossible (IOException: Device is not connected....).
Could you help with explanation of such situation? That would be very nice. Kind regards, Tom.

Anonymous said...

Thank you!
You have saved me huge trouble.
Where is the tip jar?

Anonymous said...

Thanks to Zach for the program. I also had the problem that Dan du Preez had, in my case I was not calling Dispose(). If anyone has a similar problem they should ensure that Dispose() is being called.