Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] GetRawInputDeviceInfo can not use CharSet = CharSet.Auto #439

Closed
emako opened this issue Feb 6, 2024 · 4 comments
Closed

[Bug] GetRawInputDeviceInfo can not use CharSet = CharSet.Auto #439

emako opened this issue Feb 6, 2024 · 4 comments

Comments

@emako
Copy link

emako commented Feb 6, 2024

Error Code:

// CharSet  = CharSet.Auto will get the half pcbSize size
[DllImport(Lib.User32, SetLastError = true, CharSet = CharSet.Auto)]
[PInvokeData("winuser.h", MSDNShortId = "")]
public static extern uint GetRawInputDeviceInfo(HANDLE hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize);

And then Marshal.PtrToStringAuto will get the error device name or some time will make application crash when in Chinese OS.

Like this issuse #428

I use following code to fix it.

correct code:

[DllImport("User32.dll", SetLastError = true)]
internal static extern uint GetRawInputDeviceInfo(IntPtr hDevice, RawInputDeviceInfo command, IntPtr pData, ref uint size);
@dahall
Copy link
Owner

dahall commented Feb 23, 2024

Will you share your code? I have run this under 32 and 64-bit, with Unicode character sets without problem.

uint nDev = 0;
Assert.That(GetRawInputDeviceList(null, ref nDev, (uint)Marshal.SizeOf(typeof(RAWINPUTDEVICELIST))), ResultIs.Not.Value(uint.MaxValue));
Assert.That(nDev, Is.GreaterThan(0));
RAWINPUTDEVICELIST[] devs = new RAWINPUTDEVICELIST[(int)nDev];
Assert.That(nDev = GetRawInputDeviceList(devs, ref nDev, (uint)Marshal.SizeOf(typeof(RAWINPUTDEVICELIST))), ResultIs.Not.Value(uint.MaxValue));
Assert.That(nDev, Is.GreaterThan(0));

for (int i = 0; i < nDev; i++)
{
   uint sz = 0;
   Assert.That(GetRawInputDeviceInfo(devs[i].hDevice, RIDI.RIDI_DEVICENAME, default, ref sz), ResultIs.Value(0));
   SafeLPTSTR data = new((int)sz + 1);
   Assert.That(GetRawInputDeviceInfo(devs[i].hDevice, RIDI.RIDI_DEVICENAME, data, ref sz), Is.GreaterThan(0));
   TestContext.WriteLine($"{data}");
}

@emako
Copy link
Author

emako commented Feb 24, 2024

Sample codes here:

.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

	<ItemGroup>
		<PackageReference Include="Vanara.PInvoke.Kernel32" Version="3.4.17" />
		<PackageReference Include="Vanara.PInvoke.User32" Version="3.4.17" />
	</ItemGroup>

</Project>

Program.cs

using System.Runtime.InteropServices;
using Vanara.PInvoke;

namespace ConsoleUser32;

internal class Program
{
    static void Main()
    {
        EnumerateDevices();
    }

    internal static void EnumerateDevices()
    {
        uint deviceCount = 0;
        int dwSize = Marshal.SizeOf(typeof(RawInputDeviceList));

        if (User32.GetRawInputDeviceList(null, ref deviceCount, (uint)dwSize) == 0)
        {
            User32.RAWINPUTDEVICELIST[] pRawInputDeviceList = new User32.RAWINPUTDEVICELIST[deviceCount];
            User32.GetRawInputDeviceList(pRawInputDeviceList, ref deviceCount, (uint)dwSize);

            for (int i = 0; i < deviceCount; i++)
            {
                uint pcbSize = 0;

                User32.RAWINPUTDEVICELIST rid = pRawInputDeviceList[i];
                User32.GetRawInputDeviceInfo(rid.hDevice, (uint)RawInputDeviceInfo.RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize);

                if (pcbSize <= 0) continue;

                // --------------------------------------
                // All right
                //nint pDataX = Marshal.AllocHGlobal((int)pcbSize);
                //User32X.GetRawInputDeviceInfo((nint)rid.hDevice, RawInputDeviceInfo.RIDI_DEVICENAME, pDataX, ref pcbSize);
                //string? deviceNameX = Marshal.PtrToStringAnsi(pDataX);
                //Console.WriteLine(deviceNameX);
                //Marshal.FreeHGlobal(pDataX);
                // --------------------------------------

                // --------------------------------------
                // Application crash
                nint pData = Marshal.AllocHGlobal((int)pcbSize);
                User32.GetRawInputDeviceInfo(rid.hDevice, (uint)RawInputDeviceInfo.RIDI_DEVICENAME, pData, ref pcbSize);
                string? deviceName = Marshal.PtrToStringAuto(pData);
                Console.WriteLine(deviceName);
                Marshal.FreeHGlobal(pData);
                // --------------------------------------
            }
        }
    }
}

file static class User32X
{
    [DllImport("User32.dll", SetLastError = true)]
    public static extern uint GetRawInputDeviceInfo(nint hDevice, RawInputDeviceInfo command, nint pData, ref uint size);
}

[StructLayout(LayoutKind.Sequential)]
file struct RawInputDeviceList
{
    public nint Device;
    public uint Type;
}

file enum RawInputDeviceInfo : uint
{
    RIDI_DEVICENAME = 0x20000007,
    RIDI_DEVICEINFO = 0x2000000b,
    PREPARSEDDATA = 0x20000005
}

@dahall
Copy link
Owner

dahall commented Feb 25, 2024

You have a small bug in the code due to an odd use by the API of a variable. The docs say:

For this (RIDI_DEVICENAME) uiCommand only, the value in pcbSize is the character count (not the byte count).

When calling Marshal.AllocHGlobal with the resulting value from the first call, you need to adjust for character size. This could be done by multiplying pData by Marshal.SystemDefaultCharSize or by using the SafeLPTSTR class in my sample code above. The DllImport statement then still accounts for the API having both an Ansi and a Unicode implementation.

@dahall dahall closed this as completed Feb 25, 2024
@emako
Copy link
Author

emako commented Feb 26, 2024

I ajust to nint pData = Marshal.AllocHGlobal((int)pcbSize * Marshal.SystemDefaultCharSize); and it works.
Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants