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).
58 comments:
Absolutely awesome, saved me a TON of headaches. Thank you!
Wow, you are the man!!
I've wouldn't have guessed to clear that flag...
Zach, is this code for .NET 2.0 or .NET 3.5?
Thanks
Grungean
Hi Zac, will this code with .NET 3.5 or only with .NET 2.0?
@Grungean: It's compatible with .NET 2.0 and above.
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.
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...
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.
How would I put this code into my project?
@Anonymous
As per usual.
Thanks Zach, I've been struggling with this issue for ages!
Hey Zach, you saved my life and a lot of time ;)
Best regards
PI
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.
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.
@ICAO:
Without the fix, did you have the same problem? If so, it's most likely a separate issue - perhaps hardware?
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.
Not all IOExceptions are related to the problem I posted. some could genuinely be driver bugs or your code bug.
Many thanks, this has saved us a massive headache!
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.
Sorry, nevermind, my mistake, I'm an idiot.
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++.
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.
@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?
can this be implemented in vb.net?
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!
Thanks, we've been dealing with this issue for about two weeks now. Crazy that the solution is so simple...
Thank you my computer is giving the exception even on single write() on the beginning of program. THX..
Thanks a lot - you made me happy :-)
Thank you very much..you saved me a lot of headache and pain stay blessed.
The same problem as as ICAO & Dan du Preez.
Waiting for fix.
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?
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.
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
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?
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?
Absolutely perfect, converted to VB and work perfect !!
Thank You!
@ Sean Riddle: Can u plz share your c++ code
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());
@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 >:(
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
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$!
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?
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
I still have same problem i.e "A device attached to the System is not functioning"
Thanks so much for posting this fix - I have been struggling with this problem for years.
I'm VERY grateful!
Jeff
The gift that keeps on giving. Just found your blog today and it works great. Thanks very much for the post.
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.
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...
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.
Thank you!
You have saved me huge trouble.
Where is the tip jar?
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.
System.IO.IOException was unhandled
Message=A device attached to the system is not functioning.
Source=System
StackTrace:
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 SerialPortTester.Program.Main(String[] args) in C:\Users\user\Desktop\Temp\SimpleSerialCS\SimpleSerial\Program.cs:line 216
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
Hi,
thanks for the code. I have a similar problem, but in my case it is not IOException, but ObjectDisposedException in serial port (after the port was disconnected by the device - virtual serial port).
It seems that this problem do not work in my case. Unfortunately after applying this code the port was blocked after first error (port is there even when the device was disconnected and "File not found" exception is thrown when the port is connected).
But at least I know, that the serial port in .NET framework has many bugs.
In my case, the call to m_Handle.Close() in Dispose() freezes (never returns) rendering the solution useless... Anybody else had that problem?
Stack Trace: SerialPortFixer.WinIoError()
at SerialPortFixer..ctor(String portName)
at SerialPortFixer.Execute(String portName)
Hm... why the unchecked integer in the constructor? It's a constant and the assigned value falls well within the range of System.Int32.
I'm converting to VB.NET, which doesn't have the unsigned keyword. It's true that there are some workarounds, but the ones I've seen feel klunky to me.
It appears we can leave it out here, and just use a checked System.Int32. If you feel I'm mistaken with this observation, I'd be pleased to hear more about it.
It seems that the code above is not compatible when running on .NET Core (Windows 10, 64bit version 2004)
Post a Comment