Winapi GetRawInputDeviceInfo表示RIDI_DEVICENAME的缓冲区大小为1个字符

Winapi GetRawInputDeviceInfo表示RIDI_DEVICENAME的缓冲区大小为1个字符,winapi,input,raw-input,Winapi,Input,Raw Input,我从RIDI_DEVICENAME中得到了荒谬的行为。根据文件, 返回值 类型:UINT 如果成功,此函数将返回一个非负数,指示复制到pData的字节数 如果pData对于数据来说不够大,则函数返回-1。如果pData为NULL,则函数返回一个零值。在这两种情况下,pcbSize被设置为pData缓冲区所需的最小大小 调用GetLastError以识别任何其他错误 忽略-1不是UINT返回类型中可表示的值这一明显问题,函数似乎应该告诉我所需的缓冲区大小,如果我提供了此大小的缓冲区,函数应该成功,

我从RIDI_DEVICENAME中得到了荒谬的行为。根据文件,

返回值 类型:
UINT

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

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

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

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

然而,我根本看不到这一点。在Windows 10上,当
pData
为空时,Unicode版本的函数将
pcbSize
设置为
1
,否则不使用它,在所有情况下都会失败。当
pData
为空时,该函数的ANSI版本将
pcbSize
设置为
2
,否则会将传入的任何值加倍,并且仍然失败

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

#define WIN32_EXTRA_LEAN 1

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

#include <windows.h>
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, RIDI_DEVICENAME, &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, retcode = " << validChars << L", last error = " << lasterror << L"\n";
        return {};
    }
}
std::wstring GetRawInputDeviceName( HANDLE hRaw )
{
    UINT numChars = 0u;
    INT validChars;

    validChars = static_cast<INT>(::GetRawInputDeviceInfoW(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::wstring name;
    name.resize(numChars);
    validChars = static_cast<INT>(::GetRawInputDeviceInfoW(hRaw, RIDI_DEVICENAME, &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, retcode = " << validChars << L", last error = " << lasterror << L"\n";
        return {};
    }
}
#定义WIN32_额外_精益1
#包括
#包括
#包括
#包括
#包括
ANSI测试代码:

#define WIN32_EXTRA_LEAN 1

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

#include <windows.h>
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, RIDI_DEVICENAME, &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, retcode = " << validChars << L", last error = " << lasterror << L"\n";
        return {};
    }
}
std::wstring GetRawInputDeviceName( HANDLE hRaw )
{
    UINT numChars = 0u;
    INT validChars;

    validChars = static_cast<INT>(::GetRawInputDeviceInfoW(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::wstring name;
    name.resize(numChars);
    validChars = static_cast<INT>(::GetRawInputDeviceInfoW(hRaw, RIDI_DEVICENAME, &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, retcode = " << validChars << L", last error = " << lasterror << L"\n";
        return {};
    }
}
std::string GetRawInputDeviceName(句柄hRaw)
{
UINT numChars=0u;
INT-validChars;
validChars=static_cast(::getrawinputdeviceinfo(hRaw、RIDI_DEVICENAME、nullptr和numChars));
自动lasterror=::GetLastError();
if(lasterror!=错误\u缓冲区不足){

std::wcerrGetRawInputDeviceName
在声明/实现/文档中有几个错误

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

存在3种情况:

1。函数返回负值(或者如果需要
-1
):这是错误 根据设计,必须设置最后一个错误。但事实并非如此 始终设置(实现错误)

最常见的错误:

  • pcbSize或pData指向无效或只读内存位置。在这种情况下,通常会出现错误
    error\u NOACCESS
    (翻译自
    状态\u访问\u违规

  • hDevice not valid handle-
    返回错误\u无效\u handle

  • uiCommand无效RIDI\U XXX常量-
    错误\u无效\u参数

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

    同样-仅在这种情况下(
    -1
    )存在检测调用
    GetLastError();

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

3.函数返回正值(>0),这意味着 字节(如果
RIDI\u PREPARSEDDATA
RIDI\u DEVICEINFO
)或 写入缓冲区的字符(如果
RIDI\u DEVICENAME

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

PCB尺寸 [进来,出去]

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

如果
RIDI_DEVICENAME
位于字符中

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

但在实现中存在着严重的错误。在函数的开头,句柄hDevice被转换为指针

PDEVICEINFO-PDEVICEINFO=HMValidateHandle(hDevice,键入设备信息);

(如果0返回-我们在退出时得到-1,带有
错误\u无效\u句柄

DEVICEINFO
存在
UNICODE\u字符串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;

如果(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;
然后,
getrawInputDeviceInfo
添加其他错误比较
getrawInputDeviceInfo
-由于某种原因,*pcbSize的值在2上是倍数。但是,在所有情况下都会出现此错误。 请注意,DeviceName(由上的驱动程序返回的字符串格式化)有非常严格的限制:

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

  • 小于或等于0x20(“”)
  • 大于0x7F
  • 等于0x2C(',')
因此,即使将unicode转换为ansi,设备名称的长度也将相同(所有符号均<0x80)。因此,对于ansi版本,不需要
*2
缓冲区大小


然后我已经查看了代码中的错误-您在
GetRawInputDeviceInfo
之后无条件地调用
::GetLastError();
-但返回值只有在api返回
-1
时才有意义

解释观察到的行为:

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

pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1;
因为
pDeviceInfo->ustname.Length==0
-1将在*pcbSize内返回 如果错误地返回了版本-
2*1==2
。 但是当e pass在pData中不为NULL时,我们就陷入了这个陷阱

            if (cbOutSize <= 2) { // !!!! error !!!!
                retval = -1;
                goto leave;
            }
用法示例:(
/RTCs
必须禁用)

void Demo()
{
PRAWINPUTDEVICELIST pRawInput