This is some example code that shows how to query for attached HID-driver USB devices and open a file handle to the device, allowing you to read and write to the device using a stream. The code below is demonstration quality and could use some cleanup.
Using the code below, you can enumerate all attached HID devices, and then create a FileStream object from the returned file handles.
For instance:
IList
// DiscoverDevices opens a handle to all matching devices; close the unused ones.
// Really, the API should be reworked, but the code provided is good enough for demonstration.
for( int i = 1; i < devices.Count; i++ ) { devices.FileHandle.Close(); }
// Create a FileStream to the handle we have so we can write to the device.
// Don't forget to close it when you're done!
FileStream stream = new FileStream ( devices[0].FileHandle, FileAccess.ReadWrite );
Follows the code that performs the enumeration and opening:
Enumerator.cs:
public class Enumerator
{
private static ushort VendorId = 1234;
private static ushort ProductId = 5678;
/// <summary>
/// Returns a list of serial numbers of attached USB HID devices that have
//// the given vendor ID and product ID
/// </summary>
/// <returns></returns>
public static IList<string> EnumerateAttachedDevices()
{
IList<DiscoveredDevice> devices;
List<string> serialNumbers = new List<string>();
devices = DiscoverDevices();
foreach ( DiscoveredDevice device in devices )
{
if ( serialNumbers.Contains( device.SerialNumber ) == false )
{
serialNumbers.Add( device.SerialNumber );
}
device.FileHandle.Close();
device.FileHandle.SetHandleAsInvalid();
}
return serialNumbers;
}
/// <summary>
/// Reads the serial number from the HID device pointed to by the given file handle.
/// </summary>
/// <param name="handle"></param>
/// <returns></returns>
private static string GetSerialNum( SafeFileHandle handle )
{
StringBuilder serNumBuilder = new StringBuilder( 253 );
bool success;
string result;
success = Win32Hid.HidD_GetSerialNumberString( handle, serNumBuilder, (uint)serNumBuilder.Capacity );
if ( success )
{
result = serNumBuilder.ToString();
}
else
{
result = null;
}
return result;
}
/// <summary>
/// Scans the device list for HID devices, returning open file handles to all
/// all attached devices.
/// </summary>
/// <param name="serialNum"></param>
/// <remarks>
/// If serialNum is null and not empty, the search stops at the first matching
/// device. Callers are responsible for closing the file handles.
/// </remarks>
/// <returns></returns>
public static IList<DiscoveredDevice> DiscoverDevices()
{
Guid hidGuid = Guid.Empty;
IntPtr hDevInfo; // TODO rename to hidClassInfo
bool success;
SetupDI.SP_DEVICE_INTERFACE_DATA interfaceData;
SetupDI.SP_DEVICE_INTERFACE_DETAIL_DATA interfaceDetail;
SetupDI.SP_DEVINFO_DATA devInfoData;
uint size;
uint index = 0;
List<DiscoveredDevice> resultHandles = new List<DiscoveredDevice>();
// Get the GUID for the HID device class
Win32Hid.HidD_GetHidGuid( ref hidGuid );
// Get a pointer to the device information set for the HID device class
hDevInfo = SetupDI.SetupDiGetClassDevs(
ref hidGuid,
IntPtr.Zero,
IntPtr.Zero,
SetupDI.DiGetClassFlags.Present | SetupDI.DiGetClassFlags.DeviceInterface
);
index = 0;
while ( true )
{
interfaceData = new SetupDI.SP_DEVICE_INTERFACE_DATA();
interfaceData.cbSize = (uint)Marshal.SizeOf( interfaceData );
// Using variable 'index', step through all of the devices in the HID device class.
// Fills in device interface data.
success = SetupDI.SetupDiEnumDeviceInterfaces( hDevInfo, IntPtr.Zero, ref hidGuid, index, ref interfaceData );
index++;
if ( success == false ) { break; }
devInfoData = new SetupDI.SP_DEVINFO_DATA();
devInfoData.cbSize = (uint)Marshal.SizeOf( devInfoData );
interfaceDetail = new SetupDI.SP_DEVICE_INTERFACE_DETAIL_DATA();
interfaceDetail.cbSize = IntPtr.Size == 8 ? (uint)8 : (uint)(4 + Marshal.SystemDefaultCharSize);
// Read out the device instance and path, so that we can open the device as a file.
// We don't actually need the devInfoData
success = SetupDI.SetupDiGetDeviceInterfaceDetail(
hDevInfo,
ref interfaceData,
ref interfaceDetail,
256,
out size,
ref devInfoData
);
if ( success == false ) { continue; }
// Open the device as a file so that we can query it with HID and read/write to it.
SafeFileHandle devHandle = Kernel32.CreateFile(
interfaceDetail.DevicePath,
(uint)(Kernel32.AccessRights.GENERIC_READ | Kernel32.AccessRights.GENERIC_WRITE),
(uint)(Kernel32.ShareModes.FILE_SHARE_READ | Kernel32.ShareModes.FILE_SHARE_WRITE),
IntPtr.Zero,
(uint)(Kernel32.CreationDispositions.OPEN_EXISTING),
Kernel32.Overlapped,
IntPtr.Zero
);
if ( devHandle.IsInvalid )
{
continue;
}
Win32Hid.HIDD_ATTRIBUTES hidAttribs = new Win32Hid.HIDD_ATTRIBUTES();
success = Win32Hid.HidD_GetAttributes( devHandle, ref hidAttribs );
if ( success && hidAttribs.VendorID == VendorId && hidAttribs.ProductID == ProductId )
{
DiscoveredDevice discoveredDevice;
IntPtr preparsedDataPtr = new IntPtr();
Win32Hid.HIDP_CAPS caps = new Win32Hid.HIDP_CAPS();
bool goodDevice;
// Get the device caps. We have to read out the 'preparsed data', feed that to
// GetCaps, then free the 'preparsed data'.
Win32Hid.HidD_GetPreparsedData( devHandle, ref preparsedDataPtr );
Win32Hid.HidP_GetCaps( preparsedDataPtr, ref caps );
Win32Hid.HidD_FreePreparsedData( ref preparsedDataPtr );
// If Usage is 1, we found the right instance of the device (there's three of them).
goodDevice = false;
if ( caps.Usage == 1 )
{
// Attempt to get the serial number. This could fail if the device was recently attached.
string serialNum = GetSerialNum( devHandle );
if ( serialNum != null )
{
goodDevice = true;
discoveredDevice = new DiscoveredDevice();
discoveredDevice.FileHandle = devHandle;
discoveredDevice.HidAttribs = hidAttribs;
discoveredDevice.Caps = caps;
discoveredDevice.SerialNumber = serialNum;
resultHandles.Add( discoveredDevice );
}
}
if ( goodDevice == false )
{
devHandle.Close();
devHandle.SetHandleAsInvalid();
}
}
}
success = SetupDI.SetupDiDestroyDeviceInfoList( hDevInfo );
hDevInfo = IntPtr.Zero;
return resultHandles;
}
private class DiscoveredDevice
{
public Win32Hid.HIDD_ATTRIBUTES HidAttribs { get; set; }
public Win32Hid.HIDP_CAPS Caps { get; set; }
public SafeFileHandle FileHandle { get; set; }
public string SerialNumber { get; set; }
}
}
This is support code for accessing the USB HID API and opening device handles.
Kernel32.cs
static public class Kernel32
{
public const uint Overlapped = 0x40000000;
[Flags]
public enum AccessRights : uint
{
GENERIC_READ = (0x80000000),
GENERIC_WRITE = (0x40000000),
GENERIC_EXECUTE = (0x20000000),
GENERIC_ALL = (0x10000000)
}
[Flags]
public enum ShareModes : uint
{
FILE_SHARE_READ = 0x00000001,
FILE_SHARE_WRITE = 0x00000002,
FILE_SHARE_DELETE = 0x00000004
}
public enum CreationDispositions
{
CREATE_NEW = 1,
CREATE_ALWAYS = 2,
OPEN_EXISTING = 3,
OPEN_ALWAYS = 4,
TRUNCATE_EXISTING = 5
}
[DllImport( "kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode )]
public static extern SafeFileHandle CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
[DllImport( "kernel32.dll", CharSet = CharSet.Unicode )]
public static extern void CloseHandle(
SafeHandle handle
);
[DllImport( "kernel32.dll" )]
[return: MarshalAs( UnmanagedType.Bool )]
public static extern bool CancelIo( SafeFileHandle hFile );
public delegate void OverlappedCompletionDelegate(
UInt32 dwErrorCode,
UInt32 dwNumberOfBytesTransfered,
ref NativeOverlapped lpOverlapped
);
[DllImport( "kernel32.dll", SetLastError=true )]
public static extern bool LockFile(
SafeFileHandle hFile,
uint fileOffsetLow,
uint fileOffsetHigh,
uint numberOfBytesToLockLow,
uint numberOfBytesToLockHigh
);
[DllImport( "kernel32.dll" )]
public static extern bool UnlockFile(
SafeFileHandle hFile,
uint fileOffsetLow,
uint fileOffsetHigh,
uint numberOfBytesToUnlockLow,
uint numberOfBytesToUnlockHigh
);
}
SetupDI.cs
public static class SetupDI
{
// SetupDiGetClassDevs
[DllImport( "setupapi.dll", CharSet = CharSet.Auto )]
public static extern IntPtr SetupDiGetClassDevs(
ref Guid ClassGuid,
IntPtr Enumerator,
IntPtr hwndParent,
DiGetClassFlags Flags
);
// SetupDiGetDeviceInterfaceDetail
[DllImport( @"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true )]
public static extern Boolean SetupDiGetDeviceInterfaceDetail(
IntPtr hDevInfo,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData,
UInt32 deviceInterfaceDetailDataSize,
out UInt32 requiredSize,
ref SP_DEVINFO_DATA deviceInfoData
);
// SetupDiEnumDeviceInterfaces
[DllImport( @"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true )]
public static extern Boolean SetupDiEnumDeviceInterfaces(
IntPtr hDevInfo,
ref SP_DEVINFO_DATA devInfo,
ref Guid interfaceClassGuid,
UInt32 memberIndex,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData
);
// SetupDiEnumDeviceInterfaces
[DllImport( @"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true )]
public static extern Boolean SetupDiEnumDeviceInterfaces(
IntPtr hDevInfo,
IntPtr devInfo,
ref Guid interfaceClassGuid,
UInt32 memberIndex,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData
);
[DllImport( "setupapi.dll", SetLastError = true )]
public static extern bool SetupDiDestroyDeviceInfoList( IntPtr deviceInfoSet );
[StructLayout( LayoutKind.Sequential )]
public struct SP_DEVINFO_DATA
{
public UInt32 cbSize;
public Guid ClassGuid;
public UInt32 DevInst;
public IntPtr Reserved;
}
// Device interface data
[StructLayout( LayoutKind.Sequential )]
public struct SP_DEVICE_INTERFACE_DATA
{
public UInt32 cbSize;
public Guid InterfaceClassGuid;
public UInt32 Flags;
public UIntPtr Reserved;
}
// Device interface detail data
[StructLayout( LayoutKind.Sequential, CharSet = CharSet.Auto )]
public struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
public UInt32 cbSize;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = 256 )]
public string DevicePath;
}
[Flags]
public enum DiGetClassFlags : int
{
Default = 0x00000001, // only valid with DeviceInterface
Present = 0x00000002,
AllClasses = 0x00000004,
Profile = 0x00000008,
DeviceInterface = 0x00000010,
}
}
Win32Hid.cs
public static class Win32Hid
{
// HidD_GetHidGuid
[DllImport( "hid.dll", SetLastError = true )]
public static extern void HidD_GetHidGuid( ref Guid hidGuid );
// HidD_GetAttributes
[DllImport( "hid.dll", SetLastError = true )]
public static extern Boolean HidD_GetAttributes(
SafeFileHandle HidDeviceObject,
ref HIDD_ATTRIBUTES Attributes );
// HidD_GetSerialNumberString
[DllImport( "hid.dll", SetLastError = true )]
public static extern Boolean HidD_GetSerialNumberString(
SafeFileHandle HidDeviceObject,
[MarshalAs( UnmanagedType.LPWStr )]
StringBuilder Buffer,
UInt32 BufferLength );
// HidD_GetPreparsedData
[DllImport( "hid.dll", SetLastError = true )]
public static extern bool HidD_GetPreparsedData( SafeFileHandle HidDeviceObject, ref IntPtr PreparsedData );
[DllImport( "hid.dll", SetLastError = true )]
public static extern bool HidD_FreePreparsedData( ref IntPtr PreparsedData );
[DllImport( "hid.dll", SetLastError = true )]
public static extern int HidP_GetCaps( IntPtr preparsedData, ref HIDP_CAPS capabilities );
[DllImport( "hid.dll", SetLastError = true )]
public static extern bool HidD_FlushQueue( SafeFileHandle HidDeviceObject );
public struct HIDD_ATTRIBUTES
{
public Int32 Size;
public Int16 VendorID;
public Int16 ProductID;
public Int16 VersionNumber;
}
[StructLayout( LayoutKind.Sequential )]
public struct HIDP_CAPS
{
public short Usage;
public short UsagePage;
public short InputReportByteLength;
public short OutputReportByteLength;
public short FeatureReportByteLength;
[MarshalAs( UnmanagedType.ByValArray, SizeConst = 17 )]
public short[] Reserved;
public short NumberLinkCollectionNodes;
public short NumberInputButtonCaps;
public short NumberInputValueCaps;
public short NumberInputDataIndices;
public short NumberOutputButtonCaps;
public short NumberOutputValueCaps;
public short NumberOutputDataIndices;
public short NumberFeatureButtonCaps;
public short NumberFeatureValueCaps;
public short NumberFeatureDataIndices;
}
}