GetRawInputDeviceInfo 表示 RIDI_DEVICENAME 的缓冲区大小为 1 个字符
GetRawInputDeviceInfo indicates a buffer size of 1 character for RIDI_DEVICENAME
我从 RIDI_DEVICENAME
那里得到了荒谬的行为。根据文档,
Return value
Type: UINT
If successful, this function returns a non-negative number indicating the number of bytes copied to pData
.
If pData
is not large enough for the data, the function returns -1
. If pData
is NULL
, the function returns a value of zero. In both of these cases, pcbSize
is set to the minimum size required for the pData
buffer.
Call GetLastError
to identify any other errors.
忽略 -1
在 UINT
return 类型中不是可表示值的明显问题,似乎该函数应该告诉我所需的缓冲区大小,并且如果我提供这种大小的缓冲区,该函数应该成功或至少遵循它自己的失败规则。
但是,我根本没有看到这个。在 Windows 10 上,当 pData
为 null 时,该函数的 Unicode 版本将 pcbSize
设置为 1
,否则保持不变,在所有情况下均失败。当 pData
为 null 时,该函数的 ANSI 版本将 pcbSize
设置为 2
,否则将传入的任何值加倍,但仍然失败。
Headers 用于任一版本的测试代码:
#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, 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 {};
}
}
Unicode 测试代码:
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 {};
}
}
在 Windows 10 上,通过 RDP 我一直得到 ERROR_INSUFFICIENT_BUFFER
。
在 Windows 8.1 运行 作为本地用户,如果 pData
为空,我得到 ERROR_INSUFFICIENT_BUFFER
,当我提供缓冲区时,我返回失败((UINT)-1
) 和 GetLastError()
return 为零。
我也刚刚尝试提出 likely-large-enough 缓冲区大小,但也失败了。
这是怎么回事,获取接口路径名的正确方法是什么,我需要管理权限还是先调用一些其他API?我调用 GetRawInputDeviceList
或使用 GetRawInputDeviceInfo
的 RIDI_DEVICEINFO
模式似乎没有任何问题...但我需要接口路径才能走得更远。
- Windows HID Device Name Format
GetRawInputDeviceName
在声明/实现/文档中有几个错误
实际上更正确地将 return 值声明为有符号(LONG
或 INT
)而不是 UINT
存在 3 个案例:
1. 函数 return 负值(或者如果需要 -1
):这是错误
案例和设计 - 必须设置最后一个错误。但真的不是
始终设置(执行错误)。
最常见的错误:
pcbSize 或 pData 指向无效或只读内存位置。这种情况下的常见错误 ERROR_NOACCESS
(翻译自
STATUS_ACCESS_VIOLATION
)
hDevice 无效句柄 - ERROR_INVALID_HANDLE
是 returned
uiCommand 无效 RIDI_XXX 常量 - ERROR_INVALID_PARAMETER
*pcbSize 对于数据来说不够大 - 在这种情况下 *pcbSize 设置为最小值pData 缓冲区所需的大小。 ERROR_INSUFFICIENT_BUFFER
再次 - 只有在这种情况下 (-1
) 存在意义调用 GetLastError();
2. function return 0 只有当 pData 为 NULL。
*pcbSize 设置为 pData 缓冲区所需的最小大小。
3.函数return正值(>0)这意味着这个计数
字节(如果 RIDI_PREPARSEDDATA
或 RIDI_DEVICEINFO
)或
字符(以防RIDI_DEVICENAME
)写入缓冲区
所以这里的文档是错误的:
pcbSize
[in, out]
Pointer to a variable that contains the size, in bytes, of the data in
pData.
如果 RIDI_DEVICENAME
在 个字符中
所以已经可以看出非常严重的设计问题(return 值的类型 - 无符号)和混合 bytes/characters。许多不同的案例。
但随后在实施中存在严重错误。在函数句柄的开头 hDevice 转换为指针。
PDEVICEINFO pDeviceInfo = HMValidateHandle(hDevice, TYPE_DEVICEINFO);
(如果 0 returned - 我们在 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)(我们无法控制)。因此,如果名称的长度为零 - -1
总是 returned(error #1)但最后一个错误未设置(error #2)
当然存在并且 error #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, 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;
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(UINT), 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.Buffer, 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(UINT), 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(UINT), 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
上的驱动程序 return 格式化的字符串有非常严格的限制:
If a driver returns an ID with an illegal character, the system will
bug check. Characters with the following values are illegal in an ID
for this IRP:
- Less than or equal to 0x20 (' ')
- Greater than 0x7F
- Equal to 0x2C (',')
所以即使在将 unicode 转换为 ansi 之后 - 设备名称的长度将是相同的(所有符号 < 0x80 )。所以 Ansi 版本不需要 *2
缓冲区大小。
那么我已经在你的代码中看到错误 - 你在 GetRawInputDeviceInfoW
之后无条件地调用 ::GetLastError();
- 但是 returned 值只有在 api [=203] 的情况下才有意义=] -1
解释观察到的行为:
对于本地设备 api 一般工作正确(如果我们的代码没有错误)
对于终端服务设备 - 长度为 0 ustrName
。结果,如果我们在 pData 中传递 NULL - return 值将是
pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1;
因为 pDeviceInfo->ustrName.Length == 0
- 1 将被 returned inside *pcbSize
如果 A 版本 - - 错误 - 2*1==2
将被 returned。
但是当 e 在 pData 中不通过 NULL - 我们陷入这个
if (cbOutSize <= 2) { // !!!! error !!!!
retval = -1;
goto leave;
}
因此您可以按大小传递任何缓冲区,无论如何,因为 (cbOutSize <= 2)
- -1
将被 returned 并且最后一个错误 未设置
可能的解决方案 - 首先 - 从不使用 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));
}
}
这段代码在我的电脑上运行良好。不确定,但确实可能是 RDP 问题。
UINT result = ::GetRawInputDeviceInfoW(m_Handle, RIDI_DEVICENAME, 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, RIDI_DEVICENAME, buffer.data(), &size);
if (result == static_cast<UINT>(-1))
{
//PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(size, result);
我从 RIDI_DEVICENAME
那里得到了荒谬的行为。根据文档,
Return value
Type:
UINT
If successful, this function returns a non-negative number indicating the number of bytes copied to
pData
.If
pData
is not large enough for the data, the function returns-1
. IfpData
isNULL
, the function returns a value of zero. In both of these cases,pcbSize
is set to the minimum size required for thepData
buffer.Call
GetLastError
to identify any other errors.
忽略 -1
在 UINT
return 类型中不是可表示值的明显问题,似乎该函数应该告诉我所需的缓冲区大小,并且如果我提供这种大小的缓冲区,该函数应该成功或至少遵循它自己的失败规则。
但是,我根本没有看到这个。在 Windows 10 上,当 pData
为 null 时,该函数的 Unicode 版本将 pcbSize
设置为 1
,否则保持不变,在所有情况下均失败。当 pData
为 null 时,该函数的 ANSI 版本将 pcbSize
设置为 2
,否则将传入的任何值加倍,但仍然失败。
Headers 用于任一版本的测试代码:
#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, 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 {};
}
}
Unicode 测试代码:
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 {};
}
}
在 Windows 10 上,通过 RDP 我一直得到 ERROR_INSUFFICIENT_BUFFER
。
在 Windows 8.1 运行 作为本地用户,如果 pData
为空,我得到 ERROR_INSUFFICIENT_BUFFER
,当我提供缓冲区时,我返回失败((UINT)-1
) 和 GetLastError()
return 为零。
我也刚刚尝试提出 likely-large-enough 缓冲区大小,但也失败了。
这是怎么回事,获取接口路径名的正确方法是什么,我需要管理权限还是先调用一些其他API?我调用 GetRawInputDeviceList
或使用 GetRawInputDeviceInfo
的 RIDI_DEVICEINFO
模式似乎没有任何问题...但我需要接口路径才能走得更远。
- Windows HID Device Name Format
GetRawInputDeviceName
在声明/实现/文档中有几个错误
实际上更正确地将 return 值声明为有符号(LONG
或 INT
)而不是 UINT
存在 3 个案例:
1. 函数 return 负值(或者如果需要 -1
):这是错误
案例和设计 - 必须设置最后一个错误。但真的不是
始终设置(执行错误)。
最常见的错误:
pcbSize 或 pData 指向无效或只读内存位置。这种情况下的常见错误
ERROR_NOACCESS
(翻译自STATUS_ACCESS_VIOLATION
)hDevice 无效句柄 -
ERROR_INVALID_HANDLE
是 returneduiCommand 无效 RIDI_XXX 常量 -
ERROR_INVALID_PARAMETER
*pcbSize 对于数据来说不够大 - 在这种情况下 *pcbSize 设置为最小值pData 缓冲区所需的大小。
ERROR_INSUFFICIENT_BUFFER
再次 - 只有在这种情况下 (
-1
) 存在意义调用GetLastError();
2. function return 0 只有当 pData 为 NULL。 *pcbSize 设置为 pData 缓冲区所需的最小大小。
3.函数return正值(>0)这意味着这个计数
字节(如果 RIDI_PREPARSEDDATA
或 RIDI_DEVICEINFO
)或
字符(以防RIDI_DEVICENAME
)写入缓冲区
所以这里的文档是错误的:
pcbSize [in, out]
Pointer to a variable that contains the size, in bytes, of the data in pData.
如果 RIDI_DEVICENAME
在 个字符中
所以已经可以看出非常严重的设计问题(return 值的类型 - 无符号)和混合 bytes/characters。许多不同的案例。
但随后在实施中存在严重错误。在函数句柄的开头 hDevice 转换为指针。
PDEVICEINFO pDeviceInfo = HMValidateHandle(hDevice, TYPE_DEVICEINFO);
(如果 0 returned - 我们在 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)(我们无法控制)。因此,如果名称的长度为零 - -1
总是 returned(error #1)但最后一个错误未设置(error #2)
当然存在并且 error #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, 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;
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(UINT), 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.Buffer, 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(UINT), 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(UINT), 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
上的驱动程序 return 格式化的字符串有非常严格的限制:
If a driver returns an ID with an illegal character, the system will bug check. Characters with the following values are illegal in an ID for this IRP:
- Less than or equal to 0x20 (' ')
- Greater than 0x7F
- Equal to 0x2C (',')
所以即使在将 unicode 转换为 ansi 之后 - 设备名称的长度将是相同的(所有符号 < 0x80 )。所以 Ansi 版本不需要 *2
缓冲区大小。
那么我已经在你的代码中看到错误 - 你在 GetRawInputDeviceInfoW
之后无条件地调用 ::GetLastError();
- 但是 returned 值只有在 api [=203] 的情况下才有意义=] -1
解释观察到的行为:
对于本地设备 api 一般工作正确(如果我们的代码没有错误)
对于终端服务设备 - 长度为 0 ustrName
。结果,如果我们在 pData 中传递 NULL - return 值将是
pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1;
因为 pDeviceInfo->ustrName.Length == 0
- 1 将被 returned inside *pcbSize
如果 A 版本 - - 错误 - 2*1==2
将被 returned。
但是当 e 在 pData 中不通过 NULL - 我们陷入这个
if (cbOutSize <= 2) { // !!!! error !!!!
retval = -1;
goto leave;
}
因此您可以按大小传递任何缓冲区,无论如何,因为 (cbOutSize <= 2)
- -1
将被 returned 并且最后一个错误 未设置
可能的解决方案 - 首先 - 从不使用 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));
}
}
这段代码在我的电脑上运行良好。不确定,但确实可能是 RDP 问题。
UINT result = ::GetRawInputDeviceInfoW(m_Handle, RIDI_DEVICENAME, 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, RIDI_DEVICENAME, buffer.data(), &size);
if (result == static_cast<UINT>(-1))
{
//PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(size, result);