0

I am communicating with an embedded device that periodically shuts down its USB serial port emulation, uses the USB port for something else, and then restarts its serial port emulation. During this brief period the serial port disappears from the windows device manager and also from the registry, and then reappears in both locations.

My .net 4.5 application handles this just fine so long as I am disconnected from the serial port when it occurs: after the serial port emulation has been restarted I can reconnect and communicate as usual.

However, if I am connected to the virtual serial port when the device drops and later restarts its serial port emulation, my program does not hang but I cannot gain access to the restarted port.

In this situation the windows device manager correctly shows the port vanishing and reappearing, while the registry shows the port vanishing but never reappearing. Naturally this means that SerialPort.GetPortNames() subsequently does not find the port, and of course I get an IOException if I attempt to close the port which has vanished.

Edit: I'm not using worker threads, and I am using BaseStream.ReadAsync and I am able to trap the IOException and dispose the original port object.

As I mentioned, the restarted emulated port shows up in Device Manager just fine but does not show up in the registry at HKLM/Hardware/DeviceMap/SerialComm.

Once I have disposed (or even tried and failed to close) the port in .net, if I subsequently force the embedded device to reboot then the emulated port reappears in the registry and I can reconnect to it. Unfortunately this requires physical interaction with the device, which typically is not possible.

I am seeking some way to recover programmatically, because I don't normally have access to the device to force a reboot.

I'm imagining that after I dispose my serial port object there might be some way to get Windows to refresh the registry, but I have been unable to find any clues as to how that might be accomplished.

I've found many SO questions that are similar to mine, but none that are quite the same and none that have provided an answer to my problem.

Hans Passant has suggested placing a tag on the USB cable inviting people to unplug it and plug it back in a few times just to see what happens. I like that thinking, but in my case the emulation is stopped and started by an embedded device -- and that behavior is required due to the design of the hardware.

I tried blindly adding <legacyUnhandledExceptionPolicy enabled="1"/> to my app.exe.config file, and it didn't seem to do anything.

Based on answers to similar questions I tried moving my call to Close() to a separate thread and also tried using async to await the call to close. That approach terminated my application with a system reflection error.

How can I make my application dispose its connection (or otherwise get out of the way) when a port vanishes and reappears, so the registry can be refreshed properly to match the device manager?

Or is there anything at all I can do to recover from momentary loss of an emulated serial port without terminating and restarting the application?

Craig.Feied
  • 2,617
  • 2
  • 16
  • 25

1 Answers1

1

The framework System.IO.Ports.SerialPort class certainly doesn't make this easy.

You'll need some p/invoke or C++/CLI to get the disconnect and reconnect events... the RegisterDeviceNotification function is key.

After that, you should avoid use of worker threads, because System.IO.Ports.SerialPort is going to throw exceptions when the device disappears or the port is disposed, and you won't have a chance to install proper exception handlers on the completion-port worker thread.

If you do away with the DataReceived event and instead use port.BaseStream.ReadAsync for event-driven overlapped receive, you can put a try/catch around the ReadAsync call and be able to recover by disposing the old port object. As an extra bonus, the incoming data is then delivered through the synchronization context on your main thread so you don't have to worry about thread synchronization yourself.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I have most aspects of serial comm working quite well, mostly mostly due to your famous blog post [here](http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport). However, the solution to this particular problem still eludes me. Are you suggesting that if register to receive the disconnect and reconnect events I can do something to ensure that windows will properly register the port upon reconnect? Or perhaps just that if I detect the disconnection I can quickly dispose my port before the reconnect, and that may solve the problem? – Craig.Feied Jun 13 '16 at 05:29
  • @Craig.Feied: I'm not 100% certain but yes, I think disposal during disconnect notification event processing may be the solution. That notification is synchronous, so it will actually delay the reconnect event from being processed by Windows device management, unless you do something that breaks synchronicity like a cross-thread invoke. – Ben Voigt Jun 13 '16 at 05:43
  • Unfortunately, there is not enough information in this response to call it a true answer. This only suggests a possibility of something. In my experience catching the unplugged event, and disposing of a SerialPort object during the event handling either doesn't work at all, works sporadically, or there is a very specific way of doing it that allows for another port to be constructed that can being read/write operations successfully. The first sentence is certainly a true statement! – shawn1874 Dec 07 '17 at 22:10
  • @shawn1874: The "missing information" isn't one-size-fits-all, unfortunately. From your comment it sounds like you have figured out the `RegisterDeviceNotification` call and are able to intercept the disconnect event, but then fail to cleanly close the open port, therefore leaving it locked and confusing the driver when the hardware is seen again. As I mentioned, using the port from multiple threads (including subscribing to the `DataReceived` event which uses a threadpool worker thread internally) can prevent proper cleanup. Have you refactored to use `await` instead of `DataReceived`? – Ben Voigt Dec 07 '17 at 22:43
  • @shawn1874: Another useful thing to do is run a tool that shows open files and device pseudo-files. Sysinternals (now part of Microsoft) Process Explorer is one such tool. See if the file handle got closed or it is still open after your cleanup attempt. The `System.IO.Ports.SerialPort` class has a problem with trying to do extra stuff, and if that extra stuff it does before closing the port causes an error, it ends up returning early and not actually closing the port. So you have to tiptoe around and avoid doing things that cause the extra steps to error. – Ben Voigt Dec 07 '17 at 22:47
  • @shawn1874: As a bonus, you can even use Process Explorer to force closing the handle, just to see if that is the missing piece of the puzzle and worth navigating the `System.IO.Ports.SerialPort` close-does-not-close-the-handle nightmare. – Ben Voigt Dec 07 '17 at 22:50
  • Interesting suggestions. My application is pure .net, not managed CPP. I'm using the WMI provided functionality to register for device notifications. The issue I run into is that a device removed event handler cannot close the port. It seems impossible to cleanup because by the time the event handler executes the serial port object can no longer be cleaned up. At that point the close member function of SerialPort throws. – shawn1874 Dec 08 '17 at 21:02
  • I'm not sure why you are suggesting that P/Invoke is needed. It's the part where you suggest disposing the old port that escapes me. No matter how I try to dispose/close the old port, the establishment of a new port to communicate to the same COM port fails. I am not using the the DataReceived event, I am using readline and writeline which appears to send and get strings fine. In your other blog post mentioned earlier, you've mentioned those functions are not correct which is also confusing to me. I'm not sure how those functions that you say don't work will affect proper cleanup. – shawn1874 Dec 08 '17 at 21:08
  • @shawn1874 "I'm using the WMI provided functionality to register for device notifications. The issue I run into is that a device removed event handler cannot close the port. It seems impossible to cleanup because by the time the event handler executes the serial port object can no longer be cleaned up." That's why you need to use p/invoke to call `RegisterDeviceNotification`. The WMI event is too late. But your use of `ReadLine` is contributing to the exception during `Close` as well, because `ReadLine` is using resources and preventing `Close` from releasing them. – Ben Voigt Dec 08 '17 at 23:00
  • @shawn1874: Note that the reason I wrote in my blog to avoid those functions isn't because they never work. They do work most of the time. But not all the time. During device removal is one of the times they don't work. And since they fail some of the time, you have to avoid them completely. – Ben Voigt Dec 08 '17 at 23:04