GetRawInputDeviceInfo 表示 RIDI_DEVICENAME

问题描述

我从 RIDI_DEVICENAME 那里得到了荒谬的行为。根据文档,

返回值

类型:UINT

如果成功,此函数返回一个非负数,表示复制到 pData 的字节数。

如果 pData 对数据来说不够大,函数返回 -1。如果 pDataNULL,则函数返回零值。在这两种情况下,pcbSize 都设置为 pData 缓冲区所需的最小大小。

调用 GetLastError 以识别任何其他错误

忽略 -1 不是 UINT 返回类型中可表示的值的明显问题,似乎该函数应该告诉我所需的缓冲区大小,如果我提供一个缓冲区这个大小,函数要么成功,要么至少遵循自己的失败规则。

但是,我根本没有看到这一点。在 Windows 10 上,当 pcbSize 为 null 时,函数的 Unicode 版本将 1 设置为 pData,否则将其置之不理,在所有情况下都会失败。当 pcbSize 为 null 时,函数的 ANSI 版本将 2 设置为 pData,否则将传入的任何值加倍,但仍然失败。

用于任一版本测试代码的标头:

#define WIN32_EXTRA_LEAN 1

#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

#include <windows.h>

ANSI 测试代码

std::string GetRawInputDeviceName( HANDLE hRaw )
{
    UINT numChars = 0u;
    INT validChars;

    validChars = static_cast<INT>(::GetRawInputDeviceInfoA(hRaw,RIDI_DEVICENAME,nullptr,&numChars));
    auto lasterror = ::GetLastError();
    if (lasterror != ERROR_INSUFFICIENT_BUFFER) {
        std::wcerr << L"Failed to get length of name of raw input device,retcode = " << validChars << L",last error = " << lasterror << L"\n";
        return {};
    }

    std::string name;
    name.resize(numChars);
    validChars = static_cast<INT>(::GetRawInputDeviceInfoA(hRaw,&name[0],&numChars));
    lasterror = ::GetLastError();

    if (validChars > 0) {
        name.resize(validChars);
        return name;
    }
    else {
        std::wcerr << L"Failed to get name of raw input device,last error = " << lasterror << L"\n";
        return {};
    }
}

Unicode 测试代码

std::wstring GetRawInputDeviceName( HANDLE hRaw )
{
    UINT numChars = 0u;
    INT validChars;

    validChars = static_cast<INT>(::GetRawInputDeviceInfoW(hRaw,last error = " << lasterror << L"\n";
        return {};
    }

    std::wstring name;
    name.resize(numChars);
    validChars = static_cast<INT>(::GetRawInputDeviceInfoW(hRaw,last error = " << lasterror << L"\n";
        return {};
    }
}

在通过 RDP 的 Windows 10 上,我始终获得 ERROR_INSUFFICIENT_BUFFER

在以本地用户身份运行的 Windows 8.1 上,如果 ERROR_INSUFFICIENT_BUFFER 为空,我得到 pData,当我提供缓冲区时,我得到失败 ((UINT)-1) 和 {{1} } 返回零。

我也刚刚尝试提出一个可能足够大的缓冲区大小,但也失败了。

这是怎么回事,获取接口路径名的正确方法是什么,我需要管理权限还是先调用一些其他 API?我似乎在调用 GetLastError() 或使用 GetRawInputDeviceListRIDI_DEVICEINFO 模式时没有任何问题...但我需要接口路径才能走得更远。

解决方法

GetRawInputDeviceName 在声明/实现/文档中有几个错误

事实上更正确的是将返回值声明为有符号(LONGINT)而不是 UINT

存在3种情况:

1. 函数返回负值(或者如果想要 -1):这是错误 情况下,并且按照设计 - 必须设置最后一个错误。但真的不是 始终设置(实现错误)。

最常见的错误:

  • pcbSizepData 指向无效或只读的内存位置。这种情况下的常见错误 ERROR_NOACCESS(翻译自 STATUS_ACCESS_VIOLATION)

  • hDevice 无效句柄 - 返回 ERROR_INVALID_HANDLE

  • uiCommand 无效的 RIDI_XXX 常量 - ERROR_INVALID_PARAMETER

  • *pcbSize 对于数据来说不够大 - 在这种情况下,*pcbSize 设置为 pData 缓冲区所需的最小大小。 ERROR_INSUFFICIENT_BUFFER

    再次 - 仅在这种情况下 (-1) 存在感调用 GetLastError();

2. 函数返回 0 仅当 pDataNULL 时才有可能。 *pcbSize 设置为 pData 缓冲区所需的最小大小。

3. 函数返回正值 ( > 0) 这意味着这个计数 字节(在 RIDI_PREPARSEDDATARIDI_DEVICEINFO 的情况下)或 字符(如果RIDI_DEVICENAME)写入缓冲区

所以这里的文档是错误的:

pcbSize [进,出]

指向包含数据大小的变量的指针,以字节为单位 pData

字符

中的情况下RIDI_DEVICENAME

因此在设计(返回值的类型 - 无符号)和混合字节/字符方面已经可见非常严重的问题。许多不同的情况。

但随后在实施中存在严重错误。在函数句柄的开头 hDevice 转换为指针。

PDEVICEINFO pDeviceInfo = HMValidateHandle(hDevice,TYPE_DEVICEINFO);

(如果返回 0 - 我们在使用 ERROR_INVALID_HANDLE 退出时得到 -1)。

DEVICEINFO 中存在 UNICODE_STRING ustrName - 此名称并复制到用户模式

switch (uiCommand) {
case RIDI_DEVICENAME:
    /*
     * N.b. UNICODE_STRING counts the length by the BYTE count,not by the character count.
     * Our APIs always treat the strings by the character count. Thus,for RIDI_DEVICNAME
     * only,cbOutSize holds the character count,not the byte count,in spite of its
     * name. Confusing,but cch is the way to be consistent.
     */
    cbOutSize = pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1;   // for Null terminator
    break;

   //...
}

cbOutSize相比需要cbBufferSize = *pcbSize;

if (cbBufferSize >= cbOutSize) api开始复制操作

存在下一个代码

            case RIDI_DEVICENAME:
                if (cbOutSize <= 2) { // !!!! error !!!!
                    retval = -1;
                    goto leave;
                }
                RtlCopyMemory(pData,pDeviceInfo->ustrName.Buffer,pDeviceInfo->ustrName.Length);
                ((WCHAR*)pData)[1] = '\\'; // convert nt prefix ( \??\ ) to win32 ( \\?\ )
                ((WCHAR*)pData)[cbOutSize - 1] = 0; // make it null terminated
                break;

cbOutSize 在这里 - 是设备名称的 (len + 1)(我们无法控制)。因此,如果 name 长度为零 - 始终返回 -1 (error #1) 但未设置最后一个错误 ( error #2 ) 当然存在并且错误 #3 - 为什么设备名称的长度为 0?这一定不是。但如果终端服务设备 - (在 UMB 总线上创建的虚拟鼠标/键盘设备) - 存在此结果。

api 的完整代码(在内核中)

UINT NtUserGetRawInputDeviceInfo(
    HANDLE hDevice,UINT uiCommand,LPVOID pData,PUINT pcbSize)
{
    UINT cbOutSize = 0;
    UINT cbBufferSize;
    int retval = 0;

    EnterCrit(0,UserMode);
    UserAtomicCheck uac;

    try {
        ProbeForRead(pcbSize,sizeof(UINT),sizeof(DWORD));
        cbBufferSize = *pcbSize;
    } except (EXCEPTION_EXECUTE_HANDLER) {
       UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS    
       retval = -1;
       goto leave1;
    }

    EnterDeviceInfoListCrit_();

    PDEVICEINFO pDeviceInfo = HMValidateHandle(hDevice,TYPE_DEVICEINFO);

    if (pDeviceInfo == NULL) {
        UserSetLastError(ERROR_INVALID_HANDLE); 
        retval = -1;
        goto leave;
    }

    /*
     * Compute the size of the output and evaluate the uiCommand.
     */
    switch (uiCommand) {
    case RIDI_PREPARSEDDATA:
        if (pDeviceInfo->type == DEVICE_TYPE_HID) {
            cbOutSize = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.DescriptorSize;
        } else {
            cbOutSize = 0;
        }
        break;
    case RIDI_DEVICENAME:
        /*
         * N.b. UNICODE_STRING counts the length by the BYTE count,not by the character count.
         * Our APIs always treat the strings by the character count. Thus,for RIDI_DEVICNAME
         * only,in spite of its
         * name. Confusing,but cch is the way to be consistent.
         */
        cbOutSize = pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1;   // for Null terminator
        break;

    case RIDI_DEVICEINFO:
        cbOutSize = sizeof(RID_DEVICE_INFO);
        break;

    default:
        UserSetLastError(ERROR_INVALID_PARAMETER);  
        retval = -1;
        goto leave;
    }

    if (pData == NULL) {
        /*
         * The app wants to get the required size.
         */
        try {
            ProbeForWrite(pcbSize,sizeof(DWORD));
            *pcbSize = cbOutSize;
        } except (EXCEPTION_EXECUTE_HANDLER) {
            UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS   
            retval = -1;
            goto leave;
        }
        retval = 0;
    } else {
        if (cbBufferSize >= cbOutSize) {
            try {
                ProbeForWrite(pData,cbBufferSize,sizeof(DWORD));
                switch (uiCommand) {
                case RIDI_PREPARSEDDATA:
                    if (pDeviceInfo->type == DEVICE_TYPE_HID) {
                        RtlCopyMemory(pData,pDeviceInfo->hid.pHidDesc->pPreparsedData,cbOutSize);
                    }
                    break;

                case RIDI_DEVICENAME:
                    if (cbOutSize <= 2) { // !!!!
                        retval = -1;
                        goto leave;
                    }
                    RtlCopyMemory(pData,pDeviceInfo->ustrName.Length);
                    ((WCHAR*)pData)[1] = '\\'; // make it null terminated
                    ((WCHAR*)pData)[cbOutSize - 1] = 0; // make it null terminated
                    break;

                case RIDI_DEVICEINFO:
                    {
                        PRID_DEVICE_INFO prdi = (PRID_DEVICE_INFO)pData;

                        ProbeForRead(prdi,sizeof(DWORD));
                        if (prdi->cbSize != cbOutSize) {
                            MSGERRORCLEANUP(ERROR_INVALID_PARAMETER);
                        }
                        ProbeForWrite(prdi,sizeof(RID_DEVICE_INFO),sizeof(DWORD));
                        RtlZeroMemory(prdi,sizeof(RID_DEVICE_INFO));
                        prdi->cbSize = cbOutSize;

                        switch (pDeviceInfo->type) {
                        case DEVICE_TYPE_HID:
                            prdi->dwType = RIM_TYPEHID;
                            prdi->hid.dwVendorId = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.VendorID;
                            prdi->hid.dwProductId = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.ProductID;
                            prdi->hid.dwVersionNumber = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.VersionNumber;
                            prdi->hid.usUsagePage = pDeviceInfo->hid.pHidDesc->hidpCaps.UsagePage;
                            prdi->hid.usUsage = pDeviceInfo->hid.pHidDesc->hidpCaps.Usage;
                            break;

                        case DEVICE_TYPE_MOUSE:
                            prdi->dwType = RIM_TYPEMOUSE;
                            prdi->mouse.dwId = pDeviceInfo->mouse.Attr.MouseIdentifier;
                            prdi->mouse.dwNumberOfButtons = pDeviceInfo->mouse.Attr.NumberOfButtons;
                            prdi->mouse.dwSampleRate = pDeviceInfo->mouse.Attr.SampleRate;
                            break;

                        case DEVICE_TYPE_KEYBOARD:
                            prdi->dwType = RIM_TYPEKEYBOARD;
                            prdi->keyboard.dwType = GET_KEYBOARD_DEVINFO_TYPE(pDeviceInfo);
                            prdi->keyboard.dwSubType = GET_KEYBOARD_DEVINFO_SUBTYPE(pDeviceInfo);
                            prdi->keyboard.dwKeyboardMode = pDeviceInfo->keyboard.Attr.KeyboardMode;
                            prdi->keyboard.dwNumberOfFunctionKeys = pDeviceInfo->keyboard.Attr.NumberOfFunctionKeys;
                            prdi->keyboard.dwNumberOfIndicators = pDeviceInfo->keyboard.Attr.NumberOfIndicators;
                            prdi->keyboard.dwNumberOfKeysTotal = pDeviceInfo->keyboard.Attr.NumberOfKeysTotal;
                            break;
                        }
                    }
                    break;

                default:
                    __assume(false);
                }
            } except (EXCEPTION_EXECUTE_HANDLER) {
                UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS   
                retval = -1;
                goto leave;
            }
            retval = cbOutSize;
        } else {
            /*
             * The buffer size is too small.
             * Returns error,storing the required size in *pcbSize.
             */
            retval = -1;
            try {
                ProbeForWrite(pcbSize,sizeof(DWORD));
                *pcbSize = cbOutSize;
                UserSetLastError(ERROR_INSUFFICIENT_BUFFER);
            } except (EXCEPTION_EXECUTE_HANDLER) {
                UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS   
                retval = -1;
                goto leave;
            }
        }
    }

leave:
    LeaveDeviceInfoListCrit_();
leave1:
    UserSessionSwitchLeaveCrit();

    return retval;
}

然后 GetRawInputDeviceInfoA 添加额外的错误比较 GetRawInputDeviceInfoW - 来自 *pcbSize 的值由于某种原因乘以 2。但同样 - 在所有情况下都会出现此错误。 请注意 DeviceName(从 IRP_MN_QUERY_ID 上的驱动程序返回的字符串格式化有非常严格的限制:

如果驱动程序返回带有非法字符的 ID,系统将 错误检查。具有以下值的字符在 ID 中是非法的 对于此 IRP:

  • 小于或等于 0x20 (' ')
  • 大于 0x7F
  • 等于 0x2C (',')

因此,即使在将 unicode 转换为 ansi 之后 - 设备名称的长度也将相同(所有符号 0x80 )。所以 Ansi 版本不需要 *2 缓冲区大小。


然后我已经查看了您代码中的错误 - 您在 ::GetLastError(); 之后无条件调用 GetRawInputDeviceInfoW - 但返回值仅在 api return -1 的情况下有意义

解释观察到的行为:

对于本地设备 api 一般工作正确(如果我们的代码没有错误) 对于终端服务设备 - 长度为 0 ustrName。结果,如果我们在 pData 中传递 NULL - 返回值将是

pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1;

因为 pDeviceInfo->ustrName.Length == 0 - 1 将在 *pcbSize 内返回 如果 A 版本 - - 错误 - 2*1==2 将被返回。 但是当 e 在 pData 中不传递 NULL 时 - 我们陷入了这个

            if (cbOutSize <= 2) { // !!!! error !!!!
                retval = -1;
                goto leave;
            }

所以你可以通过任何大小的缓冲区,无论如何,因为 (cbOutSize <= 2) - -1 将被返回并且最后一个错误未设置


可能的解决方案 - 首先 - 永远不要使用 ansi 版本 - GetRawInputDeviceInfoA

使用这个包装函数。

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;
    }
}

用法示例:(/RTCs 必须禁用)

void Demo()
{
    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));
    }
}
,

此代码在我的 PC 上运行良好。不确定,但确实可能是 RDP 问题。

UINT result = ::GetRawInputDeviceInfoW(m_Handle,nullptr,&size);
if (result == static_cast<UINT>(-1))
{
    //PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
    return false;
}
DCHECK_EQ(0u,result);

std::wstring buffer(size,0);
result = ::GetRawInputDeviceInfoW(m_Handle,buffer.data(),&size);
if (result == static_cast<UINT>(-1))
{
    //PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
    return false;
}
DCHECK_EQ(size,result);