调用GetRawInputDeviceInfo(RIDI_DEVICENAME)返回无效内存问题求助
问题描述
调用GetRawInputDeviceInfoA并传入RIDI_DEVICENAME指令时,期望函数将设备名字符串填充到第三个参数中并返回字符串长度,但函数返回后该参数指向无效内存(调试器显示为0x234449485c3f5c5c)。调用未显式失败:返回值为83而非-1或0,且GetLastError返回0。尝试手动分配内存、传入NULL指针及宽字符版本均得到返回值83且指向无效内存(如0x005c003f005c005c),使用设备为PS4控制器。
完整代码如下:
#include <windows.h> #include <stdint.h> #define assert(x) do{if(!(x))__debugbreak();}while(0) typedef size_t usize; typedef uint8_t u8; typedef unsigned int uint; struct Raw_Input_Memory { usize size; usize used; u8 *data; }; #define raw_input_memory_push_array(memory, type, count) (type *)raw_input_memory_push_size(memory, sizeof(type) * count) #define raw_input_memory_push_struct(memory, type) (type *)raw_input_memory_push_size(memory, sizeof(type)) void *raw_input_memory_push_size(Raw_Input_Memory *memory, usize size) { assert(memory->used + size <= memory->size); void *result = memory->data + memory->used; memory->used += size; return result; } int main() { HINSTANCE module_instance = GetModuleHandle(NULL); WNDCLASSA window_class = {}; window_class.lpfnWndProc = DefWindowProc; window_class.hInstance = module_instance; window_class.hCursor = LoadCursorA(NULL, IDC_ARROW); window_class.lpszClassName = "Toplevel"; ATOM window_class_atom = RegisterClassA(&window_class); if (window_class_atom) { HWND window = CreateWindowA(window_class.lpszClassName, "Raw Input", WS_VISIBLE | WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, module_instance, NULL); if (window) { Raw_Input_Memory raw_input_memory = {}; raw_input_memory.size = 1024 * 1024; raw_input_memory.used = 0; raw_input_memory.data = (u8 *)VirtualAlloc(NULL, raw_input_memory.size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); RAWINPUTDEVICE raw_input_device; raw_input_device.usUsagePage = 0x01; raw_input_device.usUsage = 0x05; raw_input_device.dwFlags = 0; raw_input_device.hwndTarget = 0; if (RegisterRawInputDevices(&raw_input_device, 1, sizeof(RAWINPUTDEVICE))) { MSG window_message = {}; while (GetMessageA(&window_message, NULL, 0, 0) > 0) { UINT message_kind = window_message.message; WPARAM wparam = window_message.wParam; LPARAM lparam = window_message.lParam; switch (message_kind) { case WM_QUIT: { break; } break; case WM_INPUT: { if (raw_input_memory.data) { raw_input_memory.used = 0; UINT input_size; if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, NULL, &input_size, sizeof(RAWINPUTHEADER)) != -1) { RAWINPUT *input = (RAWINPUT *)raw_input_memory_push_size(&raw_input_memory, input_size); if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, input, &input_size, sizeof(RAWINPUTHEADER)) == input_size) { assert(input->header.dwType == RIM_TYPEHID); uint device_name_length = 128; char *device_name = NULL; device_name = raw_input_memory_push_array(&raw_input_memory, char, device_name_length); assert(device_name); INT got_device_name = GetRawInputDeviceInfoA(input->header.hDevice, RIDI_DEVICENAME, &device_name, &device_name_length); if (got_device_name) { /* ... */ } } } } } break; default: { TranslateMessage(&window_message); DispatchMessageA(&window_message); } } } } } } return 0; }
错误分析
核心问题出在GetRawInputDeviceInfoA的调用参数上:
INT got_device_name = GetRawInputDeviceInfoA(input->header.hDevice, RIDI_DEVICENAME, &device_name, &device_name_length);
这里第三个参数传入了&device_name(即char**类型),但GetRawInputDeviceInfoA的第三个参数要求是LPVOID类型,当使用RIDI_DEVICENAME时,应该传入指向缓冲区的指针(char*),而不是指针的地址。
函数会将设备名直接写入你传入的地址,当你传入&device_name时,函数实际上把设备名的字符数据写到了device_name这个指针变量本身的内存中,覆盖了原本指向有效缓冲区的地址值,导致device_name变成了无效的内存地址(调试器看到的乱码值就是被覆盖后的指针内容)。
返回值83是设备名的实际长度(包含终止符),这说明函数本身执行成功,但因为参数传递错误,导致缓冲区指针被破坏。
解决方案
1. 修正参数传递
将第三个参数从&device_name改为device_name:
INT got_device_name = GetRawInputDeviceInfoA(input->header.hDevice, RIDI_DEVICENAME, device_name, &device_name_length);
2. 优化内存分配(可选但推荐)
当前代码固定分配128字节的缓冲区,可能存在长度不足的情况。更稳妥的方式是先调用一次GetRawInputDeviceInfoA获取所需的缓冲区长度,再分配对应大小的内存:
// 先获取设备名长度 INT name_length = GetRawInputDeviceInfoA(input->header.hDevice, RIDI_DEVICENAME, NULL, &device_name_length); if (name_length != -1) { // 分配足够的内存 char *device_name = raw_input_memory_push_array(&raw_input_memory, char, name_length); assert(device_name); // 再次调用获取设备名 INT got_device_name = GetRawInputDeviceInfoA(input->header.hDevice, RIDI_DEVICENAME, device_name, &name_length); if (got_device_name != -1) { // 处理设备名 } }
这样可以避免固定长度缓冲区不够的问题,确保能完整存储设备名。
补充说明
- 如果你使用宽字符版本
GetRawInputDeviceInfoW,同样需要注意参数类型匹配,传入wchar_t*而非wchar_t**。 - 确保
raw_input_memory的剩余空间足够容纳设备名,避免内存越界。
内容的提问来源于stack exchange,提问作者NicknEma

