I'm new to coding standalone applications, started a WPF project literally 4 days ago. Currently all it does is attempt to read the user's inputs from keyboards, mouses and gamepads using this native method from user32.dll:
[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);
Keyboards and mouses are handled just fine, no problems whatsoever. Input comes in and comes out as intended. However, as soon as I register my window to handle gamepads and plug in my DualSense (PS5) controller, it throws a System.AccessViolationException: "Attempted to read or write protected memory."
I've downloaded a bunch of other input handling VS projects that utilize GetRawInputData methods, and all of them have the same issue except for one written in base C. I don't mind dealing with base C to make a dll that accesses all the info i needed, but there I ran into the issue of not being unable to retrieve an int array as an out parameter for the input values.
At this point, whichever solution is easier is fine for me: A. Figuring out what causes the System.AccessViolationException or B. Figuring out how to pass an int array out of a C dll method, I just want to have a way to get the input values of my gamepad whatever it takes.
Thanks in advance to anyone who might help me with this!
Update: hRawInput is lParam of a custom Window Procedure and DataCommand and InputData are defined as follows:
public enum DataCommand : uint
{
RID_HEADER = 0x10000005, // Get the header information from the RAWINPUT structure.
RID_INPUT = 0x10000003 // Get the raw data from the RAWINPUT structure.
}
[StructLayout(LayoutKind.Sequential)]
public struct RawInputHeader
{
public uint dwType; // Type of raw input (RIM_TYPEHID 2, RIM_TYPEKEYBOARD 1, RIM_TYPEMOUSE 0)
public uint dwSize; // Size in bytes of the entire input packet of data. This includes RAWINPUT plus possible extra input reports in the RAWHID variable length array.
public IntPtr hDevice; // A handle to the device generating the raw input data.
public IntPtr wParam; // RIM_INPUT 0 if input occurred while application was in the foreground else RIM_INPUTSINK 1 if it was not.
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; // Scan code from the key depression
public ushort Flags; // One or more of RI_KEY_MAKE, RI_KEY_BREAK, RI_KEY_E0, RI_KEY_E1
private readonly ushort Reserved; // Always 0
public ushort VKey; // Virtual Key Code
public uint Message; // Corresponding Windows message for exmaple (WM_KEYDOWN, WM_SYASKEYDOWN etc)
public uint ExtraInformation; // The device-specific addition information for the event (seems to always be zero for keyboards)
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 bit header size: 24 32 bit the header size: 16
public RawData data; // Creating the rest in a struct allows the header size to align correctly for 32/64 bit
}
The struct varies dependent on its header, so it's not correct to just create a Union struct of all three types. You need to first ask for the necessary buffer size using a null
buffer, then pass a byte[]
array buffer of the correct size.
[DllImport("User32.dll")] // no SetLastError documented for this function
static extern int GetRawInputData(
IntPtr hRawInput,
DataCommand command,
[Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] byte[] pData,
[In, Out] ref int size,
int sizeHeader
);
And three separate definitions for the data, eg
[StructLayout(LayoutKind.Sequential)]
public struct InputDataHid
{
public RawInputHeader header; // 64 bit header size: 24 32 bit the header size: 16
public RawHid data; // Creating the rest in a struct allows the header size to align correctly for 32/64 bit
}
while (true)
{
var size = GetRawInputData(hRawInput, DataCommand.RID_INPUT, null, ref int actualSize, Marshal.SizeOf<RawInputHeader>());
if (actualSize < 0)
break;
var buffer = new byte[actualSize];
size = GetRawInputData(hRawInput, DataCommand.RID_INPUT, buffer, ref actualSize, Marshal.SizeOf<RawInputHeader>());
ref var header = ref Unsafe.As<byte, RawInputHeader>(ref buffer[0]);
switch (header.dwType)
{
// etc
case RIM_TYPEHID:
{
ref var input = ref Unsafe.As<byte, InputDataHid>(ref buffer[0]);
var length = input.hid.dwSizeHid * input.hid.dwCount;
if (Unsafe.ByteOffset(ref buffer[0], ref input.hid.bRawData) + length > actualSize) // sanity check
throw new Exception("Length incorrect");
var span = MemoryMarshal.CreateSpan(ref input.hid.bRawData, length)
// do stuff with span of HID data
break;
}
}
}
There is specific handling for HID reports, see the docs.