Reputation: 375
I am trying to read data from HID devices using:
uint dwsize = 0;
//RAWINPUT input = new RAWINPUT();
uint result = GetRawInputData(lParam, RID_INPUT, IntPtr.Zero, ref dwsize, Marshal.SizeOf(typeof(RAWINPUTHEADER)));
//_rawBuffer = new RAWINPUT(new RAWINPUTHEADER(), new RAWDATA(new RAWKEYBOARD(), new RAWHID(0,0)));
result = GetRawInputData(lParam, RID_INPUT, out _rawBuffer, ref dwsize, Marshal.SizeOf(typeof(RAWINPUTHEADER)));
This code works, but there is one problem getting the byte array from the RAWHID structure, according to the MSDN documentations this is how it should look:
typedef struct tagRAWHID {
DWORD dwSizeHid;
DWORD dwCount;
BYTE bRawData[1];
} RAWHID, *PRAWHID, *LPRAWHID;
This is what I have:
[StructLayout(LayoutKind.Sequential)]
internal struct RAWHID
{
public uint dwSizHid;
public uint dwCount;
public byte bRawData;
}
I have tried to look everywhere but could not find a solution that worked. I also tried Getting a IntPtr instead of a byte array and marshal copying that using this code:
int length = (int)(_rawBuffer.data.hid.dwCount * _rawBuffer.data.hid.dwSizHid);
byte[] bytes = new byte[length];
GCHandle pinnedPacket = GCHandle.Alloc(_rawBuffer.data.hid.bRawData, GCHandleType.Pinned);
Marshal.Copy(_rawBuffer.data.hid.bRawData, bytes, 0, length);
pinnedPacket.Free();
The reason I pinned it is because I keep getting the
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'
Error.
Question short: How do I make the RAWHID structure work with a byte array in C#
Upvotes: 4
Views: 1284
Reputation: 428
I also wasn't sure how to define the dynamically sized byte array BYTE bRawData[1]
in the struct so I ended up grabbing it from the original IntPtr
. I just removed it from from the RAWHID
definition:
[StructLayout(LayoutKind.Sequential)]
public struct RAWHID
{
public int Size;
public int Count;
}
[StructLayout(LayoutKind.Sequential)]
public struct RawInput
{
/// <summary>
/// Header for the data.
/// </summary>
public RawInputHeader Header;
public Union Data;
[StructLayout(LayoutKind.Explicit)]
public struct Union
{
[FieldOffset(0)] public RAWMOUSE Mouse;
[FieldOffset(0)] public RAWKEYBOARD Keyboard;
[FieldOffset(0)] public RAWHID HID;
}
}
Here's how I actual read the data and wrote it to the console:
private static void ProcessInputMessage(Message message)
{
var headerSize = (uint) Marshal.SizeOf<RawInputHeader>();
uint rawInputSize = 0;
var result = GetRawInputData(message.LParam, RID_INPUT, IntPtr.Zero, ref rawInputSize, headerSize);
if (result != 0)
{
Console.WriteLine($"Failed to get message size: {result}");
return;
}
using var rawInputPointer = new AllocHGlobalWrapper(rawInputSize);
if (!rawInputPointer.Success)
{
return;
}
result = GetRawInputData(message.LParam, RID_INPUT, rawInputPointer.Value, ref rawInputSize, headerSize);
if (result != rawInputSize)
{
Console.WriteLine($"Failed to get raw data: {result}");
return;
}
var input = Marshal.PtrToStructure<RawInput>(rawInputPointer.Value);
var buffer = new IntPtr(rawInputPointer.Value.ToInt64() + headerSize + Marshal.SizeOf<RAWHID>());
if (input.Header.Type == RawInputType.HID)
{
for(var i = 0; i < input.Data.HID.Count; i++)
{
var data = new byte[input.Data.HID.Size];
Marshal.Copy(buffer, data, i * input.Data.HID.Size, input.Data.HID.Size);
Console.WriteLine(BitConverter.ToString(data));
}
}
}
Note that AllocHGlobalWrapper
is just a simple helper I wrote to wrap calls to Marshal.AllocHGlobal(...)
in something that calls Marshal.FreeHGlobal(...)
when it's disposed, so rawInputPointer.Value
is just returning the IntPtr
that points to the memory allocated for RawInput
.
Upvotes: 2
Reputation: 375
This is not an answer using only C#, but this is how I solved it. If you know how to do it in just C# or a different way feel free to answer!
I solved it by making my own C++ dll, I do not have a lot of experience with C++ so please comment improvements. But here is my C++:
#include "stdafx.h"
#include <iostream>
extern "C" {
#include "Winuser.h"
__declspec(dllexport)
BYTE*
__cdecl
GetRawInputDataHid(_In_ HRAWINPUT hRawInput, //Returns Byte array
_In_ UINT uiCommand,
_Inout_ PUINT pcbSize,
_In_ UINT cbSizeHeader) {
UINT dwSize;
RAWINPUT *raw;
LPBYTE lpb;
dwSize = (UINT)pcbSize;
lpb = new BYTE[dwSize];
GetRawInputData(hRawInput, uiCommand, lpb, pcbSize, cbSizeHeader);
raw = (RAWINPUT*)lpb;
if (raw->header.dwType == RIM_TYPEHID) { // Checks if device is HID
INT rawDataSize = raw->data.hid.dwCount*raw->data.hid.dwSizeHid;
BYTE * returnval = new BYTE[rawDataSize];
std::memcpy(returnval, raw->data.hid.bRawData, rawDataSize); // Copies memory
if (sizeof(returnval) <= 0) { return NULL; } // Return Null of byte array length is 0 or lower
delete lpb; // Deletes lpb
return returnval; // Returns Byte array
}
delete lpb; // Deletes lpb
return NULL; // Returns null if not HID
}
__declspec(dllexport)
INT
__cdecl
freeMem(BYTE* arrayPtr) { //Frees memory
if (arrayPtr != nullptr) {
delete[] arrayPtr;
}
return 0;
}
}
In my C# I added the following:
/* OWN DLL PATH FOR TESTING */
private const string DllFilePath = @"D:\VS 2017\repos\GetRawInputDataHid\Release\GetRawInputDataHid.dll";
[DllImport(DllFilePath, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr GetRawInputDataHid(IntPtr hRawInput, uint uiCommand, ref uint pcbSize, int cbSizeHeader);
[DllImport(DllFilePath, CallingConvention = CallingConvention.Cdecl)]
public static extern int freeMem(IntPtr ptr);
And also the following if device is a HID device:
IntPtr bytesData = GetRawInputDataHid(lParam, RID_INPUT, ref dwsize, Marshal.SizeOf(typeof(RAWINPUTHEADER)));
int length = (int)(_rawBuffer.data.hid.dwCount * _rawBuffer.data.hid.dwSizHid);
byte[] bytes = new byte[length];
if (bytesData != IntPtr.Zero)
{
Marshal.Copy(bytesData, bytes, 0, length);
freeMem(bytesData);
} else
{
uint error = GetLastError();
Console.WriteLine(error);
}
Hope this may help some people out there, if you know how to do it in just C# or a different way feel free to answer.
Upvotes: 2