10

I got a little problem with a USB Barcode Scanner. I am using the Scanner with the "SerialPort" class:

        this._barcodeScanner = new SerialPort(comPort, 9600, Parity.None, 8, StopBits.One) { Handshake = Handshake.None, ReadTimeout = 500, WriteTimeout = 500 };
        this._barcodeScanner.Open();
        this._barcodeScanner.DataReceived += BarcodeScannerCallback;

If I unplug the USB Device while it´s opened via the "SerialPort" class, I can´t close the software properly and the virtual port stays open for all eternity or till I reboot the whole computer.

So my question is, is there any way to close the virtual port after I unplugged the device via C# code?

Greetings

[edit #1]

Alrighty, some more code:

This way I am checking every 10 seconds if the device is plugged in:

    private bool CheckUsbDeviceAvailability()
    {
        ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\WMI",
        "SELECT * FROM MSSerial_PortName WHERE PortName = '" + this.PortName + "'");

        if (searcher.Get().Count > 0)
            return true;
        return false;
    }

Thats the Callback-Event of the Serial Port:

void BarcodeScannerCallback(object sender, SerialDataReceivedEventArgs e)
    {
        Thread.Sleep(500);
        string data = this._barcodeScanner.ReadExisting().Replace(Convert.ToChar(2), Convert.ToChar(32)).Trim();
        if (data.StartsWith("AX"))
        {
            string[] arrData = data.Split('\n');
            this._barcodeScanner.StopAvailabilityThread();
            Barcode code = new Barcode(arrData[0].Replace("\r", ""));

            if (CheckIfBarcodeExists(code))
                this.UpdateBarcodeNode(code);
            else
                this.CreateBarcodeNode(code);

            BarcodeScannerCallbackEvent(sender, e, code);
            this._barcodeScanner.StartAvailabilityThread();
        }

        this._barcodeScanner.ComDevicePluggedIn = ScannerDevice.ComAvailabilityState.Available;
    }

if it doesnt answer anymore it will fire the "DeviceNotAvailableEvent()":

    void BarcodeScannerDeviceNotAvailableEvent()
    {
        this._barcodeScanner.Close();
        this._barcodeScanner.Dispose();
    }

I have overriden the Dispose Event of the "SerialPort" class so that it´s going to abort the Thread:

protected override void Dispose(bool isDisposing)
    {
        if (isDisposing)
        {
            this._deviceAvailableThread.Abort();

        }

        base.Dispose(isDisposing);
    }
MPelletier
  • 16,256
  • 15
  • 86
  • 137
Robert Nagel
  • 183
  • 1
  • 7
  • What did you try to close the serial port when the device has been unplugged? – Adriano Repetti Mar 23 '12 at 08:15
  • When the device is unplugged I am aborting all threads, that belong in any way to the device; I close the SerialPort itself (which does not throw an error) and I am disposing the object – Robert Nagel Mar 23 '12 at 08:19
  • editet the question with more code – Robert Nagel Mar 23 '12 at 08:34
  • That dispose override looks a little needless. Do you have something permanently running inside the thread keeping it alive? When a thread "runs out of code" it will close itself down. – Adam Houldsworth Mar 23 '12 at 08:39
  • at which point everything freezes? CheckUsbDeviceAvailability, BarcodeScannerDeviceNotAvailableEvent are called after you unplug device? – Renatas M. Mar 23 '12 at 08:44
  • @Reniuz: Yeah the event got a loop keeping it alive No, nothing is called after unplugging the device. Everything freezes when I am trying to close the software. And after I force shutdown it via task manager and plugging in the device again I can´t use this port anymore until I reboot the computer – Robert Nagel Mar 23 '12 at 08:50
  • The code to me looks like the dispose would abort the thread before disposing the SerialPort, which **might** leave unmanaged stuff lingering. Instead of aborting the thread, try to stop the event loop and then let the thread naturally end. – Adam Houldsworth Mar 23 '12 at 08:52
  • you trying to close port while closing software? – Renatas M. Mar 23 '12 at 08:56
  • no I am trying to close it before I close the software. If I close it while the device is plugged it everything works as it is supposed to be. – Robert Nagel Mar 23 '12 at 08:56
  • Have you tried to test without thread? just to open port, disconnect device, close port, close program. – Renatas M. Mar 23 '12 at 09:03
  • no, not yet. I will try it right now in a test application. I´ll tell you the result after. – Robert Nagel Mar 23 '12 at 09:07
  • @Reniuz Without a Thread it won´t work as well. I just did it as you said, but the virtual port stays busy and the application freezes if I am trying to close it. – Robert Nagel Mar 23 '12 at 09:13
  • so when calling port.Close() your app freezes right? – Renatas M. Mar 23 '12 at 09:22
  • no, nothing happens if I call port.Close(), it freezes if i close the whole app. – Robert Nagel Mar 23 '12 at 09:30
  • I just tried it with another USB-Device. Not with a barcode scanner. With that device it works as I want it to. So maybe there´s a problem with the driver of the scanner. Is there a to force-release an busy virtual com-device? – Robert Nagel Mar 23 '12 at 09:38
  • Ok actually I have no idea why this happens only with one device :/ Can you try to close port in another thread [here is an example](http://social.msdn.microsoft.com/forums/en-US/Vsexpressvcs/thread/ce8ce1a3-64ed-4f26-b9ad-e2ff1d3be0a5/) – Renatas M. Mar 23 '12 at 09:46
  • What is the class ManagementObjectSearcher? I try to implement your code but I have not the class ManagementObjectSearcher. I tried by Importing System.Management without success. – E_Blue Jun 02 '17 at 12:58

2 Answers2

26

Serial ports date from the stone age of computing. That's where you plugged in your ASR-33 teletype to start typing in your Fortran program. The electrical interface is very simple. So is the Windows API to use a serial port from your own code. Practically any runtime environment supports them.

USB has replaced serial port hardware completely. It has a much more advanced logical interface to the machine, supporting many different type of devices. And it supports Plug and Play, allowing the operating system to detect when a device is attached or removed as well as automatically installing the device driver, etcetera.

This flexibility comes at a price however, a USB device always needs a device driver to become usable. Device drivers are not created equal. Different drivers require different ways to talk to the device. Usually done through DeviceIoControl() or Read/WriteFile() but those are very opaque API functions. In the early days of USB, device manufacturers would supply a DLL that provided a rich API to hide the implementation details.

That did not work so well, manufacturers are not very good at writing good APIs and they sure don't like to support them. So a good solution would be to support a standard API, one that's available on any machine, supported by any runtime, documented and maintained by somebody else. Like the serial port API.

That did not work so well, manufacturers are not very good at writing device drivers that emulate serial ports. The biggest hang-up with the API is that it doesn't have any support for Plug and Play. The core support for it is missing, after all serial port hardware doesn't have the logical interface to support it. There is some support for detecting that a device is attached through the DTR hardware handshake line, but no support whatsoever for detecting that the port is no longer there.

Detaching the USB device is the problem. In an ideal world, the emulator built into the device driver would simply pretend that the serial port is still there until the last handle on the device is closed. That would be the logical implementation, given that there's no way to trigger a Plug and Play event. For some strange reason that seems to be difficult to implement. Most USB drivers take the crummy shortcut, they simply make the device disappear even while it is in use.

This plays havoc on any user mode code that uses the device. Which is typically written to assume it is a real serial port and real serial ports don't suddenly disappear. At least not without drawing a bright blue spark. What goes wrong is pretty unpredictable because it depends on how the driver responds to requests on a device that's no longer there. An uncatchable exception in a worker thread started by SerialPort was a common mishap. Sounds like your driver really gets it wrong, it generates an error return code on the MJ_CLOSE driver request. Which is kind of a logical thing to do for a driver, after all the device isn't there anymore, but quite unsolvable from your end. You have a handle and you can't close it. That's up a creek with no paddle.

Every major release of .NET had a small patch to the SerialPort classes to try to minimize the misery a bit. But there's a limited amount that Microsoft can do, catching all errors and pretending they didn't happen ultimately leads to class that provides no good diagnostic anymore, even with a good driver.

So practical approaches are:

  • always use the Remove Hardware Safely tray icon in Windows
  • use the latest version of .NET
  • contact the vendor and ask for a driver update
  • ditch vendors that supply lousy drivers
  • tell your users that, just because it is the only thing you can do with a USB device, that unplugging it doesn't solve any problems
  • make closing the port easy and accessible in your UI
  • glue the USB connector to the port so it can't be removed

The 5th bullet is also what gets programmers in trouble. Writing serial port code isn't easy, it is heavily asynchronous and the threadpool thread that runs the DataReceived event is difficult to deal with. When you can't diagnose the software problem you tend to blame the hardware. There's very little you can do with the hardware but unplug it. Bad Idea. Now you have two problems.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • alrighty, so i don´t have to search for the problem on my side anymore. Understanding serial ports is much clearer to me now, thank you for that;) – Robert Nagel Mar 23 '12 at 10:40
  • 3
    This is a very thorough answer. This should be the canonical answer for this type of question. – Shane Wealti Apr 03 '12 at 18:31
  • 2
    -1 for "USB has replaced serial port hardware completely." as a blanket statement. – Mark Schultheiss Aug 05 '13 at 14:58
  • 2
    I'll -1 you right back for not looking at the back of a PC for the past 15 years. – Hans Passant Aug 05 '13 at 17:24
  • 1
    @HansPassant Completely is incorrect. For example, Dell's current Optiplex line (9020) have Serial Ports. – dbasnett May 14 '14 at 10:36
  • Hehe, they even made it *look* like it teleported from the 90s. Caveat emptor, that fuzzy "looks and works just like my old PC" feeling is costing you an extra hundred bucks on the final price. Most importantly, your customer won't fall for it. – Hans Passant May 14 '14 at 12:34
  • We are the customer, and the computers weren't spec'ed to have serial ports. Maybe you do have access to Dell's pricing but I don't. I do know that you can buy non-usb serial ports, PCI based, for about the same price as a USB dongle, so I doubt that the on-board serial port added hundreds to the price. – dbasnett Jun 12 '14 at 11:27
  • There is a huge gap between "uncatchable exception on a worker thread" and "catching and swallowing all errors pretending they didn't happen". Unplugged devices aren't the first ever asynchronous error condition, after all. UDP sockets handle ICMP port unreachable by delaying the error until the next API call on the socket. Serial framing errors cause an `ErrorReceived` event to be raised where application code can handle it. Either of those approaches would be infinitely better than the all-or-nothing mentioned in the answer. – Ben Voigt Oct 30 '14 at 14:21
  • @Mark: "USB has completely replaced the UART as the ubiquitous consumer attachment for hooking peripherals to general purpose PCs." Happier? Of course, microcontrollers still have far more UARTs than USB interfaces, but those don't run .NET Framework. MicroFramework handles serial ports differently. – Ben Voigt Oct 30 '14 at 14:23
  • 1
    I agree with Mark. Although new laptops don't have real serial ports, there are many, many devices that connect with a USB cable but are serial on the device side. The issue has nothing to do with whether new computers have serial ports. SW engineers will be supporting Serial-USB virtual COM ports for decades, not because of the ports on the laptops but because of the ports on devices. These types of answers are annoying because this is and will continue to be a real problem for decades or until all serial devices in the world cease to exist. – shawn1874 Oct 14 '17 at 00:28
0

This Problem Exists in .Net 2 , 3 , 3.5 you can use framework 4 (problem does not exist in .net 4)

ali motamedi
  • 103
  • 5