none
Windows 10 with POS HID barcode scanner: hidscanner.dll driver always crashes upon cancelled read operation RRS feed

  • Question

  • Hi!

    I believe I have found a serious bug with the hidscanner.dll driver that Windows 10 automatically uses for barcode scanners that are POS HID compatible. This issue has cost me about one week's work and I believe I've come to a point where it's time to report this issue to the outside world. This will be a pretty lengthy report, but I'm trying to include all details needed. For those of you who may have come here because you have this issue: this post does include a very effective work-around!

    The background

    First of all, the background: I work with developing and maintaining a CMS for an online retailer. We have a custom built Windows Forms application for handling everything related to the site. We use barcode scanners for handling the merchandise. The particular scanner we use (this is not critical to the issue) is the Intermec SG20T. We have configured all our scanners to work in USB POS HID mode.

    Our application makes use of PoS for .NET 1.12 and implements a simple HID reader class to read data from the device. I will include some code below, but let me first describe what we do and the problem we're running into:

    When we want to read data from the SG20T, we simply open the scanner's HID device path as a file (WINAPI CreateFile()), read any received data (via .NET's FileStream.BeginRead()) and report data events to the application. It's a simple and clean implementation that has worked perfectly for Windows 7.

    When we updated from Windows 7 to Windows 10, the scanner was still identified correctly by the system, but we started noticing issues in our application. While the scanner could be activated by our application and we could scan barcodes like before, the scanner would occasionally disconnect from USB (LED turned off, scanner not usable). By unplugging and then plugging the scanner back in, it would start working again. After a while, it would once again disconnect. And so on.

    I started looking into the issue and soon identified the following events in Event Viewer -> Windows Logs -> System:

    Source: DriverFrameworks-UserMode
    Event ID: 10110
    Level: Critical
    User: SYSTEM
    Description: A problem has occurred with one or more user-mode drivers and the hosting process has been terminated. This may temporarily interrupt your ability to access the devices.

    Immediately followed by:

    Source: DriverFrameworks-UserMode
    Event ID: 10111
    Level: Critical
    User: SYSTEM
    Description: The device POS HID Barcode scanner (location (unknown)) is offline due to a user-mode driver crash. Windows will attempt to restart the device 5 more times. Please contact the device manufacturer for more information about this problem.

    After lots of tests, looking at our code and Googling around, I've come to the following conclusion:

    This issue was introduced because Windows 10 uses a different driver for POS HID barcode scanners than Windows 7. Windows 7 uses the generic "HID-compliant device", which I believe uses hidclass.sys via input.inf. Windows 10 defaults to "POS HID Barcode scanner" using hidscanner.dll via hidscanner.inf.

    So, what's wrong with the hidscanner.dll driver? Well, it appears that it can't handle I/O cancellations correctly, at least not for reads. This is a major problem for a barcode scanner. What happens in our application is:

    1. During activation of the scanner, the application retrieves a file handle to the scanner, by calling the WINAPI CreateFile() function.
    2. A read thread is set off. What it does is that it opens a .NET FileStream on the scanner file handle. It then calls FileStream.BeginRead() asyncronously and starts waiting for one of two things: read operation completion or a manual thread termination event.
    3. Here's where the issue arises: With a barcode scanner, the application can never now when/if the next data arrives. The BeginRead() function waits for data indefinitely if we don't scan anything. Since it was called asynchronously, we're able to end the thread cleanly by singalling from the calling thread. However, doing so will always result in Windows' hidscanner.dll driver crashing, with the previously listed event log printouts.

    Normally, when exiting a thread, any outstanding read operations will be cancelled automatically by Windows. This works as expected with the old "HID-compliant device" driver, i.e. no driver crash. With the new hidscanner.dll driver, any attempt to cancel an outstanding read will invariably result in the driver crashing. Manually cancelling the read by calling the WINAPI functions CancelIo() or CancelIoEx() does not help (the driver crashes as soon as those functions are called). It simply seems as if the driver can't behave when it receives a cancellation.

    It's worth noting that if we rewrite our application to wait for the outstanding read to complete and then exit, we can make the application exit cleanly without any driver crash. However, hanging the program until the operator performs a dummy scan is obviously not a very clean solution...

    The workaround

    The workaround to this whole issue is comprised of two steps: 1) Make the barcode scanner use the generic HID driver and 2) Disable Windows' Enhanced Power Management for the device to prevent it from immediately going into sleep.

    Change driver:

    1. Go into the Device Manager.
    2. Find the category "POS Barcode Scanner", expand it and double click on "POS HID Barcode scanner".
    3. Click the "Driver" tab and then the "Update Driver..." button.
    4. Click "Browse my computer for driver software".
    5. Click "Let me pick from a list of device drivers on my computer".
    6. There should now be two compatible drivers shown. One is the already active and malfunctioning "POS HID Barcode scanner" and the other is the generic "HID-compliant device" driver. Choose "HID-compliant device" and click "Next" and then "Close".
    7. Remaining on the properties page for the scanner, click the "Details" tab and choose "Hardware Ids" from the dropdown list. Write down the VID and PID values shown, for example VID_067E and PID_0809. These will be needed further down.

    With the new driver installed, Windows seems to apply its "Enhanced Power Management" function to the device. This causes the scanner to go into low power mode and become inaccessible within a few seconds. To work around this, perform the following steps:

    1. Start the Registry Editor by pressing start, writing "regedit" and pressing enter.
    2. Go to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\
    3. Find the entry that matches the VID and PID found in step 7 above, i.e. VID_067E&PID_0809. Expand it.Do the following step (4) for any entries found under the expanded key:
    4. Expand key to reveal "Device Parameters". Click it. In the Right pane, double click "EnhancedPowerManagementEnabled" and set the value to 0. Click the "OK" button. If "EnhancedPowerManagementEnabled" doesn't exist, create it as a 32-bit DWORD value and set its value to 0.
    5. I believe step 4 has to be repeated if the barcode scanner is moved to another USB port, where it hasn't been connected before, since that will create a new key for that particular port.


    The procedure for disabling enhanced power management is taken from the following Microsoft article: https://support.microsoft.com/en-us/kb/2900614

    Unplug the barcode scanner and plug it back in. It should now stay enabled. With this workaround, the barcode scanner works correctly with our application. No more driver crashes when aborting an outstanding read operation.

    The root cause

    I believe this issue is caused by issues in hidscanner.dll, but I don't really understand how this hasn't been caught/fixed/documented already. Maybe we're accessing our barcode scanners in a way that others don't, but I still think it's pretty standard to simply read data from the device path. So why does hidscanner.dll have this regression compared to the older generic driver? I think we need some Microsoft staff in here to answer this.

    I will provide some code below that makes it easy to reproduce the issue. Below is a simple WIN32 console application that reads from a hard coded device path. Simply exchange my device path (devicePath variable) with one that matches your barcode scanner, compile and run the application.

    The application will block on the ReadFile() call and wait for you to scan a barcode. Open the Event Viewer -> Windows Logs -> System before continuing. Scan a barcode. The application will read the scanned data, present it and exit.

    Run the application again. This time, when the application blocks on ReadFile(), press Ctrl+C to end. Observe the event log. You will see that two new critical events have occurred (update the log to see them). These are the events that report the driver crash.

    Feel free to test the same procedure after performing the workaround presented earlier in this post. No events will be reported in the event log when using the generic driver.

    #include "targetver.h"
    #include <stdio.h>
    #include <tchar.h>
    #include <windows.h>
    #include "iostream"
    
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	// Exchange the device path below with the one for your barcode scanner:
    	LPCWSTR devicePath = L"\\\\?\\HID#VID_067E&PID_0809#6&294709D5&0&0000#{4D1E55B2-F16F-11CF-88CB-001111000030}";
    
        HANDLE hout = CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    
        if(hout == INVALID_HANDLE_VALUE)
        {
            cout<<"Error when creating handle! Exiting...";
    		return 0;
        }
    
    	cout<<"Handle created!\n\n";
    
    	TCHAR buff[64]={};
    	DWORD nrOfBytesRead;
    
    	cout<<"Calling ReadFile() to read data from scanner...\n";
    
    	if (!ReadFile(hout, buff, sizeof(buff), &nrOfBytesRead, NULL))
    	{
    		cout<<"ReadFile was not successful (returned 'false')!\n";
    		cout<<"GetLastError() returns ";
    		cout<<GetLastError();
    		cout<<"\n\n";
    	}
    
    	cout<<"Nr of bytes read: ";
    	cout<<nrOfBytesRead;
    	cout<<"\n\n";
    
    	if (nrOfBytesRead > 0)
    	{
    		for (int i=0; i<63; i++)
    		{
    			cout<<buff[i];
    		}
    
    		cout<<"\n\n";
    	}
    
        CloseHandle(hout);
    	return 0;
    }

    Below is the loop used in the read thread of our application:

    using (FileStream fs = new FileStream(HidHandle, FileAccess.Read, InputReportByteLength, true))
    {
        byte[] buffer = new byte[InputReportByteLength];
    
        while (true)
        {
            IAsyncResult res = fs.BeginRead(buffer, 0, InputReportByteLength, AsyncCallback, null);
    
            // We need to wait for either IO to complete or the read to be signaled
            WaitHandle[] IOCompleteOrThreadTerminating = { res.AsyncWaitHandle, ThreadTerminating };
    
            // Wait for data or thread termination
            if (1 == WaitHandle.WaitAny(IOCompleteOrThreadTerminating))
                break;
    
    
            // Call endRead to get the # of bytes actually read.  This will throw an exception
            // if the read was not successfull (i.e. the device was removed, etc).
            int bytesRead = fs.EndRead(res);
            Logger.Info(devicename, "read " + bytesRead.ToString(CultureInfo.InvariantCulture) + " bytes from device.");
    
            // Make sure we got the correct # of bytes
            if (bytesRead == InputReportByteLength)
            {
                // Get strong ref to callback delegate
                DataReadCallback callback = wdcallback.Target as DataReadCallback;
                if (callback == null)
                    break;
    
                // report data to caller
                callback(buffer);
    
                callback = null;
            }
            else
            {
                // Unexpected data size - we'll thrown an exception for now.
                // This may need to change for devices that report variable length data.
                throw new PosControlException("Unexpected number of bytes returned from device.", ErrorCode.Failure);
            }
        }
    }

    As you can see, we start the asynchronous read and then wait for either read completion or a thread termination. When we terminate and the thread exits, the hidscanner.dll driver crashes. It doesn't matter if we manually call CancelIo() or CancelIoEx(), the driver will crash in the same way (although it crashes immediately on the CancelIo/CancelIoEx call and not on thread exit).

    We have tried setting the COMMTIMEOUTS struct, but the timeout values do not seem to be honored by the underlying driver.

    So, there you have it. I've spent a lot of time on this and I now believe it's something Microsoft needs to fix. We're probably not alone in having this issue, since it's a breaking change that's non-trivial to work around when coming from Windows 7.


    • Edited by Brunnis Wednesday, July 6, 2016 1:39 PM
    Wednesday, July 6, 2016 12:54 PM

Answers

All replies