9
votes

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);
    }
2
What did you try to close the serial port when the device has been unplugged?Adriano Repetti
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 objectRobert Nagel
editet the question with more codeRobert Nagel
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
at which point everything freezes? CheckUsbDeviceAvailability, BarcodeScannerDeviceNotAvailableEvent are called after you unplug device?Renatas M.

2 Answers

23
votes

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.

0
votes

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