View Full Version : C# Enumerating HID on Vista x64
ghell
11-06-2007, 03:32 AM
I'm (still) trying to get HID devices to enumerate on 64bit Windows Vista.
The following code is cut down to the bone from the original source (http://www.codeproject.com/useritems/USB_HID.asp) just to enumerate the devices and count them out (doesn't even display any information about them).
This works fine on 32bit XP and 32bit Vista (Correct output should display something similar to "Device 0 Device 1 Device 2") . On 64bit vista (tested on 3 computers with a range of USB devices plugged in) it does not find anything so the output is just blank. I have not tested on 64bit XP.using System;
using System.Runtime.InteropServices;
public class MyUSBTest : Win32Usb
{
public static void Main()
{
try
{
// Get HID GUID
Guid gHid;
HidD_GetHidGuid(out gHid);
// Get device list
IntPtr hInfoSet = SetupDiGetClassDevs(ref gHid, null, IntPtr.Zero, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
try
{
// Set up interface
DeviceInterfaceData oInterface = new DeviceInterfaceData();
oInterface.Size = Marshal.SizeOf(oInterface);
// Loop through devices
int nIndex = 0;
while (SetupDiEnumDeviceInterfaces(hInfoSet, 0, ref gHid, (uint)nIndex, ref oInterface))
{
// Output the index and device path
Console.WriteLine("Device {0}", nIndex++);
//string strDevicePath = HIDDevice.GetDevicePath(hInfoSet, ref oInterface);
//Console.WriteLine(strDevicePath);
}
// The following prints 1784 (ERROR_INVALID_USER_BUFFER) when not working (64 bit) and
// 259 (ERROR_NO_MORE_ITEMS) when working (32 bit)
Console.WriteLine("Error: {0}", Marshal.GetLastWin32Error());
}
finally
{
SetupDiDestroyDeviceInfoList(hInfoSet);
}
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
}
}
}
public class Win32Usb
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
protected struct DeviceInterfaceData
{
public int Size;
public Guid InterfaceClassGuid;
public int Flags;
public int Reserved;
}
protected const int DIGCF_PRESENT = 0x02;
protected const int DIGCF_DEVICEINTERFACE = 0x10;
[DllImport("hid.dll", SetLastError = true)]
protected static extern void HidD_GetHidGuid(out Guid gHid);
[DllImport("setupapi.dll", SetLastError = true)]
protected static extern IntPtr SetupDiGetClassDevs(ref Guid gClass, [MarshalAs(UnmanagedType.LPStr)] string strEnumerator, IntPtr hParent, uint nFlags);
[DllImport("setupapi.dll", SetLastError = true)]
protected static extern int SetupDiDestroyDeviceInfoList(IntPtr lpInfoSet);
[DllImport("setupapi.dll", SetLastError = true)]
protected static extern bool SetupDiEnumDeviceInterfaces(IntPtr lpDeviceInfoSet, uint nDeviceInfoData, ref Guid gClass, uint nIndex, ref DeviceInterfaceData oInterfaceData);
}Does anyone have any idea how to get this working on 64 bit (the slightest idea will do)?
Someone suggested to use GetLastError so I have added this to the code (bold code) and the output seems to be helpful. However, I have no idea how to resolve the problem that it highlights.
ghell
11-07-2007, 06:34 PM
Changing the struct to[StructLayout(LayoutKind.Sequential, Pack = 1)]
protected struct DeviceInterfaceData
{
public int Size;
public Guid InterfaceClassGuid;
public int Flags;
public IntPtr Reserved; // should correspond to ULONG_PTR but was an int
}Finally did the trick.
Now on to a different part of the library (HIDDevice.GetDevicePath)
Here's the code, modified to work. In the original in the library it just had oDetail.Size = 5;but on 64bit it only works with a size of 8.[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DeviceInterfaceDetailData
{
public int Size;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string DevicePath;
}
public static string GetDevicePath(IntPtr hInfoSet, ref DeviceInterfaceData oInterface)
{
DeviceInterfaceDetailData oDetail = new DeviceInterfaceDetailData();
// Size workaround
if (IntPtr.Size == 8)
oDetail.Size = 8;
else
oDetail.Size = 5;
Console.WriteLine("Size of struct: {0}", Marshal.SizeOf(oDetail)); // 4 + 256 = 260
uint nRequiredSize = 0;
// Error 0
if (!SetupDiGetDeviceInterfaceDetail(hInfoSet, ref oInterface, IntPtr.Zero, 0, ref nRequiredSize, IntPtr.Zero))
// Error 122 - ERROR_INSUFFICIENT_BUFFER (not a problem, just used to set nRequiredSize)
if (SetupDiGetDeviceInterfaceDetail(hInfoSet, ref oInterface, ref oDetail, nRequiredSize, ref nRequiredSize, IntPtr.Zero))
return oDetail.DevicePath;
// Error 1784 - ERROR_INVALID_USER_BUFFER (unless size=5 on 32bit, size=8 on 64bit)
return null;
}
My question is why does the size need to be 5 on 32 bit but 8 on 64 bit. I do not see where these numbers come from.
The documentation of the function (http://msdn2.microsoft.com/en-us/library/ms792901.aspx) says that it should be set with sizeof. It specifically mentions "The cbSize member always contains the size of the fixed part of the data structure, not a size reflecting the variable-length string at the end".
Shouldn't Marshal.SizeOf(oDetail) return these numbers? The fact that it doesn't (it returns 260, which includes the 256 byte string but even without that it always returns 4, not 5 or 8) implies that the data structure is defined incorrectly. Even if the struct was defined incorrectly, I can't see any way that the fixed part of it would be 5 or 8 bytes long.
The documentation for the struct is here (http://msdn2.microsoft.com/en-us/library/ms793116.aspx).
kfank
01-31-2009, 03:09 AM
I have followed the same path as ghell. After making his modifications I am still stumbling on the second call to SetupDiGetDeviceInterfaceDetail() because I am passing in a pointer to a SP_DEVINFO_DATA struct.
HidDevInfo = APIs.SetupDiGetClassDevs(ref HidGuid,
null,
null,
APIs.DIGCF_PRESENT | APIs.DIGCF_DEVICEINTERFACE);
Index = 0;
DevInterfaceData.Size = (uint)Marshal.SizeOf(DevInterfaceData);
Result = APIs.SetupDiEnumDeviceInterfaces(HidDevInfo,
0,
ref HidGuid,
Index,
ref DevInterfaceData);
if (Result == false)
{
return;
}
// The first call will return an error condition, but we'll get
// the size of the structure.
DIDResult = APIs.SetupDiGetDeviceInterfaceDetail(HidDevInfo,
ref DevInterfaceData,
null,
0,
out DataSize,
null);
/* allocate memory for the HidDevInfo structure */
DevInterfaceDetailData =
new APIs.SP_DEVICE_INTERFACE_DETAIL_DATA();
// Set the size parameter of the structure. This member always contains the size of
// the fixed part of the SP_DEVICE_INTERFACE_DETAIL_DATA structure, not a size
// reflecting the variable-length string at the end.
DevInterfaceDetailData.Size = 5;
DevInfoData.ClassGuid = System.Guid.Empty;
DevInfoData.DevInst = IntPtr.Zero;
DevInfoData.Reserved = IntPtr.Zero;
DevInfoData.cbSize = (UInt32)Marshal.SizeOf(DevInfoData);
// Now call the function with the correct size parameter. This
// function will return data from one of the array members that
// Step #2 pointed to. This way we can start to identify the
// attributes of particular HID devices.
DIDResult = APIs.SetupDiGetDeviceInterfaceDetail(HidDevInfo,
ref DevInterfaceData,
ref DevInterfaceDetailData,
DataSize,
out RequiredSize,
out DevInfoData);
ghell
01-31-2009, 01:47 PM
When I got this working and made a project with it, I extracted the HID code from the part specific to my device and published it as libhidnet (http://sourceforge.net/projects/libhidnet) on sourceforge.
The library works on Windows (32 and 64bit) and Linux (32 and 64bit at least) so you can look at the Windows source in there to see how I've gone about doing things if you want.
This is the part that is letting you down:DevInterfaceDetailData.Size = 5;as SetupAPI packs the structs to 8 bytes on 64 bit, so this needs to be rounded up to the nearest 8 on 64 bit (hence it is 5 on 32, 8 on 64)
From Win32Hid.cs:
public static string[] DevicePaths
{
get
{
List<string> paths = new List<string>();
// Get GUID
Guid gHid;
Win32Hid.HidD_GetHidGuid(out gHid);
// Get info set
IntPtr hInfoSet = Win32Hid.SetupDiGetClassDevs(ref gHid, null, IntPtr.Zero, Win32Hid.DIGCF_DEVICEINTERFACE | Win32Hid.DIGCF_PRESENT);
try
{
// Set up interface
Win32Hid.SP_DEVICE_INTERFACE_DATA oInterface = new Win32Hid.SP_DEVICE_INTERFACE_DATA();
oInterface.Size = Marshal.SizeOf(oInterface);
// Loop through devices
uint nIndex = 0;
while (Win32Hid.SetupDiEnumDeviceInterfaces(hInfoSet, 0, ref gHid, nIndex++, ref oInterface))
{
// Add device paths
Win32Hid.SP_DEVICE_INTERFACE_DETAIL_DATA oDetail = new Win32Hid.SP_DEVICE_INTERFACE_DETAIL_DATA();
// Size of fixed part of struct
// TODO find solution to this rather than workaround
if (IntPtr.Size == 8)
oDetail.Size = 4 + 4;
else
oDetail.Size = 4 + 1; // 4+Marshal.SystemDefaultCharSize = 6 on Vista and does not work.
// Maximum size of unfixed part of struct
int maxSize = Marshal.OffsetOf(typeof(Win32Hid.SP_DEVICE_INTERFACE_DETAIL_DATA), "DevicePath").ToInt32() + (Marshal.SystemDefaultCharSize * 256);
int nRequiredSize;
if (Win32Hid.SetupDiGetDeviceInterfaceDetail(hInfoSet, ref oInterface, ref oDetail, maxSize, out nRequiredSize, IntPtr.Zero))
paths.Add(oDetail.DevicePath);
}
}
finally
{
Win32Hid.SetupDiDestroyDeviceInfoList(hInfoSet);
}
return paths.ToArray();
}
}
Note that the MSDN documentation recommends you make 2 passes on the function, where the first fails (as I had done in the original 2 posts here) but this code that I used in libhidnet only makes 1 pass. The difference really is that it always gives it the maximum it will need rather than letting the first fail to find out how much it wants exactly.
kfank
02-03-2009, 02:31 AM
ghell,
I did not intend to nor realize that my previous reply had been posted to this thread. I had already gotten this to work by modifying as you said:
DevInterfaceDetailData.Size = 8;
(I did try downloading your libhidnet but did not find any source code.)
However, to build on this, my next problem that I have not been able to solve is to be able to navigate the USB tree once I have a Device Instance handle. My code works in 32-bit mode but not 64-bit mode. In my second call to SetupDiGetDeviceInterfaceDetail() I am passing in a pointer to a SP_DEVINFO_DATA struct to get the DevInst handle:
[StructLayout(LayoutKind.Sequential)]
internal struct SP_DEVINFO_DATA
{
public int cbSize;
public Guid ClassGuid;
public IntPtr DevInst;
public IntPtr Reserved;
}
[DllImport("setupapi.dll")]
public static extern int CM_Get_Parent(out IntPtr pdnDevInst,
IntPtr dnDevInst,
int ulFlags
);
// The first call will return an error condition, but we'll get
// the size of the structure.
DIDResult = APIs.SetupDiGetDeviceInterfaceDetail(HidDevInfo,
ref DevInterfaceData,
null,
0,
out DataSize,
null);
/* allocate memory for the HidDevInfo structure */
DevInterfaceDetailData = new APIs.SP_DEVICE_INTERFACE_DETAIL_DATA();
DevInfoData = new APIs.SP_DEVINFO_DATA();
// Set the size parameter of the structure. This member always contains the size of
// the fixed part of the SP_DEVICE_INTERFACE_DETAIL_DATA structure, not a size
// reflecting the variable-length string at the end.
if (IntPtr.Size == 8) // 64-bit
{
DevInterfaceDetailData.Size = 8;
/* Through trial and error I have determined that cbSize must be set to 32 in
order for the call to SetupDiGetDeviceInterfaceDetail() to succeed. (I
guess this makes sense if all fields of the struct are 8-byte aligned.)*/
DevInfoData.cbSize = 32;
}
else // 32-bit
{
DevInterfaceDetailData.Size = 4 + Marshal.SystemDefaultCharSize;
DevInfoData.cbSize = (UInt32)Marshal.SizeOf(DevInfoData);
}
DevInfoData.ClassGuid = System.Guid.Empty;
DevInfoData.DevInst = IntPtr.Zero;
DevInfoData.Reserved = IntPtr.Zero;
// Now call the function with the correct size parameter. This
// function will return data from one of the array members that
// Step #2 pointed to. This way we can start to identify the
// attributes of particular HID devices.
DIDResult = APIs.SetupDiGetDeviceInterfaceDetail(HidDevInfo,
ref DevInterfaceData,
ref DevInterfaceDetailData,
DataSize,
out RequiredSize,
out DevInfoData);
if (DIDResult != false)
{
int RetCode;
IntPtr DevInst = IntPtr.Zero;
/*This call should return a device instance handle to the HID device's
parent node but instead it fails with return code 5: CR_INVALID_DEVNODE */
RetCode = APIs.CM_Get_Parent(out DevInst, DevInfoData.DevInst, 0);
}
The HID device is downstream of a USB hub. Why is Get_CM_Parent() failing when SetupDiGetDeviceInterfaceDetail() indicates success?
ghell
02-03-2009, 03:12 AM
The source code is available in the same way that the source code is available through any sourceforge project (on the downloads page, as a file in the package). I assume you have ever used sourceforge for something else before.
The last snippet I gave returns a string array of device paths on Windows 32 and 64 bit. All you need to do is use that.
Just do something like this:
foreach(string path in DevicePaths)
{
// open the file handle (System.IO.FileStreams work here)
// check the vendor ids or whatever you need (marshal the HidD_* and HidP_* functions)
// read and/or write to the device
// close the handle
}
Get_CM_Parent() should not be needed (I have never used it anyway)
kfank
02-03-2009, 06:53 PM
ghell,
Ah, I grabbed the .zip file thinking it was the same as the tar.gz. Thanks.
Regarding CM_Get_Parent(), I am using this to navigate the USB tree. Once I get a device instance handle to my HID device I need to search for a non-HID sibling device. I can't directly use CM_Get_Sibling() because I am not guaranteed that my HID device is the first one in the chain. So I have to:
CM_Get_Parent()
CM_Get_Child() // returns first child node in list of siblings
while not found
CM_Get_Sibling() // returns next sibling in list
For my application, I know that there is a hub present that my HID device is plugged into, so I am guaranteed that I can traverse the tree in this manner. Unfortunately, I cannot get the CM_Get_Parent() to work on 64-bit. Does anyone know what's wrong?
ghell
02-03-2009, 11:01 PM
The only information I could find for "DEVINST" was under SP_DEVINFO_DATA (http://msdn.microsoft.com/en-us/library/ms792997.aspx), which defines it as a DWORD.
It could be that you want to use an int (Int32) rather than an IntPtr for that parameter to CM_Get_Parent (http://msdn.microsoft.com/en-us/library/ms791198.aspx). If you pass an IntPtr on 32bit, it is the same size anyway and although it is a pointer that is being changed, it is always treated as a 32 bit block of memory, not a pointer, so it doesn't matter. On 64bit, you are passing in a 64bit block of memory which may be causing the problem.
The first parameter is a pointer to a DEVINST so that should still be IntPtr, though I don't know if you would use "out" and "IntPtr" at the same time, it should probably either be "out uint pdnDevInst" or "IntPtr pdnDevInst" (or ref?)
Try[DllImport("setupapi.dll")]
public static extern uint CM_Get_Parent(out uint pdnDevInst, uint dnDevInst, uint ulFlags);
I don't know about the "CMAPI" return type though, I couldn't find anything on that either.
kfank
02-04-2009, 08:33 PM
Yes, this is the problem. The information on pinvoke.net is incorrect for SP_DEVINFO_DATA for the 64-bit platform. DEVINST is declared as a DWORD so the correct structure declaration should be:
[StructLayout(LayoutKind.Sequential)]
struct SP_DEVINFO_DATA
{
public UInt32 cbSize;
public Guid ClassGuid;
public UInt32 DevInst;
public IntPtr Reserved;
}
In addition, the CM_* declarations are incorrect on pinvoke.net. Because, as mentioned above, DEVINST is a DWORD, IntPtr should not be used. For example, CM_Get_Parent() should be defined as:
[DllImport("setupapi.dll")]
static extern int CM_Get_Parent(
out UInt32 pdnDevInst,
UInt32 dnDevInst,
int ulFlags
);
I will update the declarations on pinvoke.net.
ghell
02-04-2009, 10:42 PM
I have yet to find anything on pinvoke.net that works at all on 64bit (which means it isn't working properly on 32bit either, it just happens to work by coincidence), so I just avoid it entirely and use MSDN + common sense.
It isn't that hard to work out that pointers get IntPtr, 32bit ints get Int32, unsigned 32bit ints get UInt32, 64bit ints get Int64, etc so I don't know what people were thinking when they added these things to pinvoke.net in the first place.
Is it working for you now?
kfank
02-05-2009, 10:33 PM
Yes, working now.
pinvoke.net just saves typing, but if the info is wrong it doesn't necessarily save time. :-(
vBulletin® v3.8.2, Copyright ©2000-2012, Jelsoft Enterprises Ltd.