Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HidD_* functions all fail due to an invalid HID handle (Windows HID C++ API)

I am having issues getting usage data from a Windows Precision Touchpad in my C++ desktop application. I have gotten the following working:

  • Registered for raw input
  • I am receiving WM_INPUT messages containing raw data
  • I can call GetRawInputData() to return a RAWINPUT structure

My next step has been trying to use the Hidpi.h utilities to determine which usages my touchpad support (HidP_GetCaps(), HidP_GetValueCaps(), etc...), and then get their values. To get things going, I am simply trying to print X, Y, tip switch, contact id, and max contact count).

In order to use the aforementioned utilities, I noticed from documentation that I generally need 2 things: Preparsed data (PHIDP_PREPARSED_DATA) and the actual Input or Feature Report.

This is where things started going wrong. Everything I have read said I should have been able to use HidD_GetPreparsedData() to get the first item. The HID device handle I'm passing is raw_data_buffer->header.hDevice, where raw_data_buffer is the PRAWINPUT type I populated from GetRawInputData(). Unfortunately, this fails. Calling GetLastError() returns ERROR_INVALID_HANDLE. I was unable to find any explanation...the handle wasn't NULL and matched the HID device I was trying to read from.

I found a different method to get the preparsed data:

...
preparsed_data_buffer = (PHIDP_PREPARSED_DATA)malloc( pp_data_buf_sz );
GetRawInputDeviceInfo( raw_data_buffer->header.hDevice, RIDI_PREPARSEDDATA, preparsed_data_buffer, &pp_data_buf_sz );
...

As far as I can tell, the data is correct. I was able to then able to use *_GetCaps() and _*GetValueCaps() to see supported usages.

11 Input Button Capabilites Found
Index:   0, IsRange: 0, UsagePage: 0X000D, Usage: 0X0042
Index:   1, IsRange: 0, UsagePage: 0X000D, Usage: 0X0047
Index:   2, IsRange: 0, UsagePage: 0X000D, Usage: 0X0042
Index:   3, IsRange: 0, UsagePage: 0X000D, Usage: 0X0047
Index:   4, IsRange: 0, UsagePage: 0X000D, Usage: 0X0042
Index:   5, IsRange: 0, UsagePage: 0X000D, Usage: 0X0047
Index:   6, IsRange: 0, UsagePage: 0X000D, Usage: 0X0042
Index:   7, IsRange: 0, UsagePage: 0X000D, Usage: 0X0047
Index:   8, IsRange: 0, UsagePage: 0X000D, Usage: 0X0042
Index:   9, IsRange: 0, UsagePage: 0X000D, Usage: 0X0047
Index:  10, IsRange: 0, UsagePage: 0X0009, Usage: 0X0001

17 Input Value Capabilites Found
Index:   0, IsRange: 0, UsagePage: 0X000D, Usage: 0X0051
Index:   1, IsRange: 0, UsagePage: 0X0001, Usage: 0X0030
Index:   2, IsRange: 0, UsagePage: 0X0001, Usage: 0X0031
Index:   3, IsRange: 0, UsagePage: 0X000D, Usage: 0X0051
Index:   4, IsRange: 0, UsagePage: 0X0001, Usage: 0X0030
Index:   5, IsRange: 0, UsagePage: 0X0001, Usage: 0X0031
Index:   6, IsRange: 0, UsagePage: 0X000D, Usage: 0X0051
Index:   7, IsRange: 0, UsagePage: 0X0001, Usage: 0X0030
Index:   8, IsRange: 0, UsagePage: 0X0001, Usage: 0X0031
Index:   9, IsRange: 0, UsagePage: 0X000D, Usage: 0X0051
Index:  10, IsRange: 0, UsagePage: 0X0001, Usage: 0X0030
Index:  11, IsRange: 0, UsagePage: 0X0001, Usage: 0X0031
Index:  12, IsRange: 0, UsagePage: 0X000D, Usage: 0X0051
Index:  13, IsRange: 0, UsagePage: 0X0001, Usage: 0X0030
Index:  14, IsRange: 0, UsagePage: 0X0001, Usage: 0X0031
Index:  15, IsRange: 0, UsagePage: 0X000D, Usage: 0X0056
Index:  16, IsRange: 0, UsagePage: 0X000D, Usage: 0X0054

3 Feature Value Capabilites Found
Index:   0, IsRange: 0, UsagePage: 0X000D, Usage: 0X0059
Index:   1, IsRange: 0, UsagePage: 0X000D, Usage: 0X0055
Index:   2, IsRange: 0, UsagePage: 0XFF00, Usage: 0X00C5

I found everything I expected. X (x1/x30), Y (x1/x31), and Contact ID (xD/x51) should be in my Input Report, the Tip Switch would be a button, and Max Contact Count (xD/x55) would be in my Feature Value Report.

Focusing on the Input/Feature reports next, I am running into my original problem. The 7th parameter to HidP_GetUsageValue() is a PCHAR to either the input or feature reports, but I don't know how to get them. HidD_GetInputReport() and HidD_GetFeature() both fail with the same invalid handle error I was seeing trying to get the preparsed data through that API.

After going through probably 10 threads or examples, I noticed somebody using their raw (unparsed) data to get input values as such:

HidP_GetUsageValue( HidP_Input, usages[ usage_idx ].page, 0, usages[ usage_idx ].usage, &val,
                    preparsed_data, (PCHAR)raw_data_buffer->data.hid.bRawData, hid_capabilities.InputReportByteLength );

This worked. I can get x, y, and contact ids from the input report...I guess because the raw input report must be the first block of raw data. However, I still can't get the feature report using HidP_GetUsageValue( HidP_Feature... because I don't know where that report is in the raw input.

I have spent over 10 hours researching for possible causes and workarounds, and have come up with nothing. For the purposes of my application I might be able to get away with just the Input Report values, but there has to be something I'm missing.

6/14 UPDATE: @RbMm @psmears Thank you for your comments. I didn't realize that the return from GetRawInputDeviceInfo(), using RIDI_DEVICENAME, was the symbolic path needed to create a file for my device. I didn't need to use SetupApi or cfgmgr32 to go through all the device interfaces. I'm still unable to create a file for my device though. Since I can see the path I'm looking for while enumerating all of the device interfaces, I decided to test by creating a new file for each one to rule out path format/encoding issues. Here was my output:

CM_Get_Device_Interface_List() found device #1.
Path: \\?\HID#VID_1FD2&PID_6103&MI_00&Col02#9&885ad2f&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
CrateFile() worked.

CM_Get_Device_Interface_List() found device #2.
Path: \\?\HID#VID_046D&PID_C077#6&b39df8e&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
CreateFile() failed with error: 0x5

CM_Get_Device_Interface_List() found device #3.
Path: \\?\HID#VID_B404&PID_0101&MI_01&Col01#7&22822429&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
CreateFile() failed with error: 0x20

CM_Get_Device_Interface_List() found device #4.
Path: \\?\HID#VID_B404&PID_0101&MI_01&Col02#7&22822429&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
CreateFile() failed with error: 0x20

CM_Get_Device_Interface_List() found device #5.
Path: \\?\HID#VID_1FD2&PID_6103&MI_01#9&20493974&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
CrateFile() worked.

CM_Get_Device_Interface_List() found device #6.
Path: \\?\HID#VID_0424&PID_274C&MI_01#8&2d3dcce0&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
CrateFile() worked.

CM_Get_Device_Interface_List() found device #7.
Path: \\?\HID#VID_B404&PID_0101&MI_01&Col03#7&22822429&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030}\KBD
CreateFile() failed with error: 0x5

CM_Get_Device_Interface_List() found device #8.
Path: \\?\HID#VID_B404&PID_0101&MI_00#7&3a45b06e&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}\KBD
CreateFile() failed with error: 0x5

CM_Get_Device_Interface_List() found device #9.
Path: \\?\HID#VID_1FD2&PID_6103&MI_00&Col01#9&885ad2f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
CreateFile() failed with error: 0x20

CM_Get_Device_Interface_List() found device #10.
Path: \\?\HID#VID_05AC&PID_0265&MI_00&Col01#8&7741f1a&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
CrateFile() worked.

CM_Get_Device_Interface_List() found device #11.
Path: \\?\HID#VID_05AC&PID_0265&MI_00&Col02#8&7741f1a&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
CrateFile() worked.

CM_Get_Device_Interface_List() found device #12.
Path: \\?\HID#VID_05AC&PID_0265&MI_01&Col02#8&1f37ab5f&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
CrateFile() worked.

CM_Get_Device_Interface_List() found device #13.
Path: \\?\HID#VID_05AC&PID_0265&MI_01&Col03#8&1f37ab5f&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030}
CrateFile() worked.

CM_Get_Device_Interface_List() found device #14.
Path: \\?\HID#VID_05AC&PID_0265&MI_02#8&36fb37a4&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
CrateFile() worked.

CM_Get_Device_Interface_List() found device #15.
Path: \\?\HID#VID_05AC&PID_0265&MI_03#8&1323f9e2&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
CrateFile() worked.

CM_Get_Device_Interface_List() found device #16.
Path: \\?\HID#VID_05AC&PID_0265&MI_01&Col01#8&1f37ab5f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
THIS is the device we want a file handle for. Path matches what we expect.
CreateFile() failed with error: 0x20

The device I'm wanting to connect to (#16 here) is the touchpad. The last several interfaces are associated with the same Apple Trackpad device, and creating a file for them works. The error "ERROR_FILE_NOT_FOUND (0x2)" doesn't make any sense to me.

Here is how I'm creating the file:

file_handle = CreateFile( comp_hid_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );

According to the MS documentation for OPEN_ALWAYS: "If the specified file does not exist and is a valid path to a writable location, the function creates a file and the last-error code is set to zero." That implies that this isn't a valid, writable location, but it definitely shows up when enumerating my interfaces.

Am I unable to open a file to the device since it was just sent raw input using WM_INPUT message?

like image 898
DJL Avatar asked Oct 29 '25 01:10

DJL


2 Answers

A "handle" is a very generic thing. Any opaque pointer-sized value we can call a "handle".

HidD_GetPreparsedData requires a file handle. This handle must be opened with CreateFileW or NtOpenFile, NtCreateFile. and this handle must be closed with CloseHandle or NtClose once the handle is no longer in use.

The hDevice in RAWINPUTDEVICELIST is not a file handle. This is some opaque value, which does not need to be closed. GetRawInputDeviceList returns an array of RAWINPUTDEVICELIST and does not say that you need to close hDevice.

You can list all hid interfaces in the system by calling CM_Get_Device_Interface_ListW with GUID_DEVINTERFACE_HID. Then pass the returned strings to CreateFileW and in this way you get a valid file handle, which can be used when calling HidD_GetPreparsedData.

For instance:

Enum(&GUID_DEVINTERFACE_HID);

// must be /RTCs-

volatile const UCHAR guz = 0;

void Enum(const GUID* InterfaceClassGuid)
{
    CONFIGRET cr;
    ULONG cb = 0, rcb;
    union {
        PVOID buf;
        PZZWSTR Buffer;
    };

    PVOID stack = alloca(guz);
    do 
    {
        cr = CM_Get_Device_Interface_List_SizeW(&rcb, const_cast<GUID*>(InterfaceClassGuid), 0, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);

        if (cr != CR_SUCCESS)
        {
            break;
        }

        if (cb < (rcb *= sizeof(WCHAR)))
        {
            cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
        }

        cr = CM_Get_Device_Interface_ListW(const_cast<GUID*>(InterfaceClassGuid), 
            0, Buffer, cb/sizeof(WCHAR), CM_GET_DEVICE_INTERFACE_LIST_PRESENT);

    } while (cr == CR_BUFFER_SMALL);

    if (cr == CR_SUCCESS)
    {
        while (*Buffer)
        {
            DumpInterface(Buffer);
            Buffer += wcslen(Buffer) + 1;
        }
    }
}

My implementation of DumpInterface:

void GetPropertyByDeviceID(PCSTR prefix, DEVINST dnDevInst)
{

    HANDLE hFile;
    IO_STATUS_BLOCK iosb;
    UNICODE_STRING ObjectName;
    OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE };


    CONFIGRET status;

    ULONG cb = 0, rcb = 0x80;

    PVOID stack = alloca(guz);

    DEVPROPTYPE PropertyType;

    union {
        PVOID pv;
        PWSTR sz;
        PBYTE pb;
    };

    static struct  
    {
        CONST DEVPROPKEY *PropertyKey;
        PCWSTR PropertyName;
    } PropertyKeys[] = {
        { &DEVPKEY_Device_PDOName, L"PDOName"},
        { &DEVPKEY_Device_Parent, L"Parent"},
        { &DEVPKEY_Device_DriverVersion, L"DriverVersion"},
        { &DEVPKEY_Device_LocationInfo, L"LocationInfo"},
        { &DEVPKEY_Device_FirmwareVersion, L"FirmwareVersion"},
        { &DEVPKEY_Device_Model, L"Model"},
        { &DEVPKEY_NAME, L"NAME"},
        { &DEVPKEY_Device_InstanceId, L"DeviceID"}
    };

    do 
    {
        int n = RTL_NUMBER_OF(PropertyKeys);

        do 
        {
            CONST DEVPROPKEY *PropertyKey = PropertyKeys[--n].PropertyKey;

            do 
            {
                if (cb < rcb)
                {
                    rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
                }

                status = CM_Get_DevNode_PropertyW(dnDevInst, PropertyKey, &PropertyType, pb, &rcb, 0);

                if (status == CR_SUCCESS)
                {
                    if (PropertyType == DEVPROP_TYPE_STRING)
                    {
                        DbgPrint("%s%S=[%S]\n", prefix, PropertyKeys[n].PropertyName, sz);

                        if (!n)
                        {
                            // DEVPKEY_Device_PDOName can use in NtOpenFile

                            RtlInitUnicodeString(&ObjectName, sz);

                            if (0 <= NtOpenFile(&hFile, FILE_READ_ATTRIBUTES|SYNCHRONIZE, &oa,
                                &iosb, FILE_SHARE_VALID_FLAGS, FILE_SYNCHRONOUS_IO_NONALERT))
                            {
                                NtClose(hFile);
                            }
                        }

                    }
                }

            } while (status == CR_BUFFER_SMALL);

        } while (n);

        if (!*--prefix) break;

    } while (CM_Get_Parent(&dnDevInst, dnDevInst, 0) == CR_SUCCESS);
}

void DumpInterface(PCWSTR InterfaceLink)
{
    DbgPrint("\r\n--------\r\n%S\r\n\r\n", InterfaceLink);

    DEVPROPTYPE PropertyType;

    union {
        PBYTE PropertyBuffer;
        PVOID buf;
        DEVINSTID_W pDeviceID;
        PWSTR PDOName;
    };

    ULONG cb = 0, rcb = 0x80;
    CONFIGRET cr;
    PVOID stack = alloca(guz);

    do 
    {
        if (cb < rcb)
        {
            cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
        }

        cr = CM_Get_Device_Interface_PropertyW(InterfaceLink, &DEVPKEY_Device_InstanceId, 
            &PropertyType, PropertyBuffer, &(rcb = cb), 0);

    } while (cr == CR_BUFFER_SMALL);

    DEVINST dnDevInst;

    if (cr == CR_SUCCESS && 
        PropertyType == DEVPROP_TYPE_STRING && 
        CR_SUCCESS == CM_Locate_DevNodeW(&dnDevInst, pDeviceID, CM_LOCATE_DEVNODE_NORMAL ))
    {
        char prefix[64];
        memset(prefix, '\t', _countof(prefix));
        prefix[_countof(prefix) - 1] = 0;
        //DbgPrint("%S\n", pDeviceID);
        GetPropertyByDeviceID(prefix + _countof(prefix) - 1, dnDevInst);
    }
}

Or if we have RAWINPUTDEVICELIST as input - we can get the device interface name with RIDI_DEVICENAME. We can use this name to call CreateFileW to get a file handle for the device.

ULONG GetRawInputDeviceInfoExW(_In_opt_ HANDLE hDevice, 
                               _In_ UINT uiCommand, 
                               _Inout_updates_bytes_to_opt_(*pcbSize, *pcbSize) LPVOID pData, 
                               _Inout_ PUINT pcbSize)
{
    switch (int i = GetRawInputDeviceInfoW(hDevice, uiCommand, pData, pcbSize))
    {
    case 0:
        return ERROR_INSUFFICIENT_BUFFER;
    case 1:
        return ERROR_INVALID_NAME;
    default:
        if (0 > i)
        {
            return GetLastError();
        }
        *pcbSize = i;

        return NOERROR;
    }
}

void DemoRI()
{
    PRAWINPUTDEVICELIST pRawInputDeviceList = 0;
    UINT uiNumDevices = 0; 
    UINT cch, cchAllocated = 0;
    union {
        PVOID buf;
        PWSTR name;
    };

    buf = 0;

    while (0 <= (int)GetRawInputDeviceList(pRawInputDeviceList, &uiNumDevices, sizeof(RAWINPUTDEVICELIST)))
    {
        if (pRawInputDeviceList)
        {
            do 
            {
                HANDLE hDevice = pRawInputDeviceList->hDevice;

                ULONG dwError;
                while (ERROR_INSUFFICIENT_BUFFER == (dwError = 
                    GetRawInputDeviceInfoExW(hDevice, RIDI_DEVICENAME, name, &(cch = cchAllocated))))
                {
                    if (cch > cchAllocated)
                    {
                        cchAllocated = RtlPointerToOffset(buf = alloca((cch - cchAllocated) * sizeof(WCHAR)), 
                            pRawInputDeviceList) / sizeof(WCHAR);
                    }
                    else
                    {
                        __debugbreak();
                    }
                }

                if (dwError == NOERROR)
                {
                    DbgPrint("[%p, %x %S]\n", hDevice, pRawInputDeviceList->dwType, name);
                }
                else
                {
                    DbgPrint("error = %u\n", dwError);
                }

            } while (pRawInputDeviceList++, --uiNumDevices);

            break;
        }
        pRawInputDeviceList = (PRAWINPUTDEVICELIST)alloca(uiNumDevices * sizeof(RAWINPUTDEVICELIST));
    }
}
like image 154
RbMm Avatar answered Oct 30 '25 17:10

RbMm


According to this microsoft sample, you can use BOOLEAN GetFeature (PHID_DEVICE HidDevice) to get feature. And wish the sample works for you.

like image 28
YangXiaoPo - MSFT Avatar answered Oct 30 '25 18:10

YangXiaoPo - MSFT