Ian.V
Ian.V

Reputation: 375

RAWINPUT RAWHID Structure in C# Byte Array

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

Answers (2)

geniusburger
geniusburger

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

Ian.V
Ian.V

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

Related Questions