You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用user32.dll读取DualSense手柄输入时触发System.AccessViolationException

WPF中使用GetRawInputData读取PS5手柄触发内存访问异常的问题

问题背景

刚接触独立应用开发,4天前启动了一个WPF项目,通过user32.dll的原生方法GetRawInputData读取键盘、鼠标及游戏手柄输入,P/Invoke定义如下:

[DllImport("User32.dll", SetLastError = true)]
internal static extern int GetRawInputData(IntPtr hRawInput, DataCommand command, [Out] out InputData buffer, [In, Out] ref int size, int cbSizeHeader);

[DllImport("User32.dll", SetLastError = true)]
internal static extern int GetRawInputData(IntPtr hRawInput, DataCommand command, [Out] IntPtr pData, [In, Out] ref int size, int sizeHeader);

键盘和鼠标输入处理完全正常,但注册窗口处理游戏手柄并插入PS5 DualSense控制器后,程序抛出System.AccessViolationException:“Attempted to read or write protected memory.”。

下载了多个使用GetRawInputData的VS输入处理项目,发现除了一个纯C编写的项目外,其余均存在相同问题。不介意用纯C编写DLL获取所需信息,但遇到无法将int数组作为输出参数返回输入值的问题。

目前希望找到任一可行方案:

  • A. 找出触发System.AccessViolationException的原因
  • B. 实现从C DLL方法中传出int数组

补充定义代码

hRawInput为自定义窗口过程的lParam,相关枚举和结构体定义如下:

public enum DataCommand : uint
{
    RID_HEADER = 0x10000005, // 获取RAWINPUT结构的头部信息
    RID_INPUT = 0x10000003   // 获取RAWINPUT结构的原始数据
}

[StructLayout(LayoutKind.Sequential)]
public struct RawInputHeader
{
    public uint dwType;                     // 原始输入类型(RIM_TYPEHID 2, RIM_TYPEKEYBOARD 1, RIM_TYPEMOUSE 0)
    public uint dwSize;                     // 整个输入数据包的字节大小,包含RAWINPUT及RAWHID变长数组中的额外输入报告
    public IntPtr hDevice;                  // 生成原始输入数据的设备句柄
    public IntPtr wParam;                   // 应用在前台时为RIM_INPUT 0,后台时为RIM_INPUTSINK 1

    public override string ToString()
    {
        return string.Format("RawInputHeader\n dwType : {0}\n dwSize : {1}\n hDevice : {2}\n wParam : {3}", dwType, dwSize, hDevice, wParam);
    }
}

[StructLayout(LayoutKind.Sequential)]
internal struct RawHid
{
    public uint dwSizHid;
    public uint dwCount;
    public byte bRawData;

    public override string ToString()
    {
        return string.Format("Rawhib\n dwSizeHid : {0}\n dwCount : {1}\n bRawData : {2}\n", dwSizHid, dwCount, bRawData);
    }
}

[StructLayout(LayoutKind.Explicit)]
internal struct RawMouse
{
    [FieldOffset(0)]
    public ushort usFlags;
    [FieldOffset(4)]
    public uint ulButtons;
    [FieldOffset(4)]
    public ushort usButtonFlags;
    [FieldOffset(6)]
    public ushort usButtonData;
    [FieldOffset(8)]
    public uint ulRawButtons;
    [FieldOffset(12)]
    public int lLastX;
    [FieldOffset(16)]
    public int lLastY;
    [FieldOffset(20)]
    public uint ulExtraInformation;
}

[StructLayout(LayoutKind.Sequential)]
internal struct RawKeyboard
{
    public ushort Makecode;                 // 按键按下的扫描码
    public ushort Flags;                    // RI_KEY_MAKE、RI_KEY_BREAK、RI_KEY_E0、RI_KEY_E1中的一个或多个
    private readonly ushort Reserved;       // 始终为0
    public ushort VKey;                     // 虚拟键码
    public uint Message;                    // 对应的Windows消息,例如WM_KEYDOWN、WM_SYSKEYDOWN等
    public uint ExtraInformation;           // 事件的设备特定附加信息(键盘通常为0)

    public override string ToString()
    {
        return string.Format("Rawkeyboard\n Makecode: {0}\n Makecode(hex) : {0:X}\n Flags: {1}\n Reserved: {2}\n VKeyName: {3}\n Message: {4}\n ExtraInformation {5}\n",
                                            Makecode, Flags, Reserved, VKey, Message, ExtraInformation);
    }
}

[StructLayout(LayoutKind.Explicit)]
public struct RawData
{
    [FieldOffset(0)]
    internal RawMouse mouse;
    [FieldOffset(0)]
    internal RawKeyboard keyboard;
    [FieldOffset(0)]
    internal RawHid hid;
}

[StructLayout(LayoutKind.Sequential)]
public struct InputData
{
    public RawInputHeader header;           // 64位系统头部大小24字节,32位系统16字节
    public RawData data;                    // 将剩余部分放在结构体中,确保32/64位系统头部对齐正确
}

解决方案

A. 排查内存访问异常的原因

异常的核心问题是**RawHid结构体的定义错误**:

  • 原生Windows的RAWHID结构体中,bRawData是一个可变长度的字节数组(长度为dwSizHid * dwCount),但当前C#定义中把它声明为单个byte。当读取PS5手柄这类HID设备的输入时,GetRawInputData会向bRawData的位置写入远超过1字节的数据,直接越界访问内存,触发AccessViolationException。

修复步骤:

  1. 改用IntPtr版本的GetRawInputData:不要用out InputData buffer的重载,因为InputData的大小是固定的,无法容纳变长的HID数据。正确流程是:
    • 先调用GetRawInputData,传入RID_HEADER命令,获取输入数据的总大小dwSize;
    • 用Marshal.AllocHGlobal分配对应大小的非托管内存;
    • 再次调用GetRawInputData,传入RID_INPUT命令,将数据读取到分配的内存中;
    • 手动解析内存中的RawHid部分:先读取dwSizHid和dwCount,然后从bRawData的偏移位置读取dwSizHid * dwCount个字节,转换为实际的手柄输入数据。
  2. 修正RawHid结构体定义:将bRawData改为IntPtr(或者直接不定义,手动计算偏移),避免固定长度限制:
    [StructLayout(LayoutKind.Sequential)]
    internal struct RawHid
    {
        public uint dwSizHid;
        public uint dwCount;
        // 移除bRawData,通过偏移手动读取
    }
    

B. 纯C DLL返回int数组的实现

如果选择用C DLL处理手柄输入,可通过以下方式传出int数组:

C DLL代码

#include <windows.h>
#include <stdlib.h>

// 导出函数:获取手柄输入数组,返回1表示成功,0表示失败
__declspec(dllexport) int GetControllerInput(int** outputArray, int* arrayLength) {
    // 示例:返回4个手柄输入值(左摇杆X、Y,右摇杆X、Y)
    *arrayLength = 4;
    // 分配内存存储数组
    *outputArray = (int*)malloc(*arrayLength * sizeof(int));
    if (*outputArray == NULL) {
        return 0;
    }
    // 替换为实际读取PS5手柄HID数据的逻辑
    (*outputArray)[0] = 127;  // 左摇杆X(示例值)
    (*outputArray)[1] = -45;  // 左摇杆Y(示例值)
    (*outputArray)[2] = 89;   // 右摇杆X(示例值)
    (*outputArray)[3] = 0;    // 右摇杆Y(示例值)
    return 1;
}

// 导出函数:释放C DLL分配的内存
__declspec(dllexport) void FreeInputArray(int* array) {
    if (array != NULL) {
        free(array);
    }
}

C#调用代码

[DllImport("YourDllName.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int GetControllerInput(out IntPtr outputArrayPtr, out int arrayLength);

[DllImport("YourDllName.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void FreeInputArray(IntPtr arrayPtr);

// 使用示例
void ReadControllerInput() {
    IntPtr arrayPtr;
    int length;
    if (GetControllerInput(out arrayPtr, out length)) {
        int[] inputArray = new int[length];
        // 将非托管内存中的数据复制到托管数组
        Marshal.Copy(arrayPtr, inputArray, 0, length);
        // 处理手柄输入数据
        Console.WriteLine($"左摇杆X: {inputArray[0]}, Y: {inputArray[1]}");
        Console.WriteLine($"右摇杆X: {inputArray[2]}, Y: {inputArray[3]}");
        // 释放非托管内存,避免泄漏
        FreeInputArray(arrayPtr);
    }
}

内容的提问来源于stack exchange,提问作者The Great ReLLeRtR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 17:48:16