├── .gitignore ├── HID_Demo.sln ├── LICENSE ├── README.md ├── Properties └── AssemblyInfo.cs ├── HID_Demo.csproj ├── Program.cs └── HIDInterface.cs /.gitignore: -------------------------------------------------------------------------------- 1 | [Bb]in 2 | [Oo]bj 3 | *.suo 4 | *.user -------------------------------------------------------------------------------- /HID_Demo.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HID_Demo", "HID_Demo.csproj", "{DEB4DCCC-72AC-4057-BF73-BF35BC3BD117}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {DEB4DCCC-72AC-4057-BF73-BF35BC3BD117}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {DEB4DCCC-72AC-4057-BF73-BF35BC3BD117}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {DEB4DCCC-72AC-4057-BF73-BF35BC3BD117}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {DEB4DCCC-72AC-4057-BF73-BF35BC3BD117}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 jcoenraadts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hid-sharp 2 | 3 | Hid-Sharp is designed to take the pain out of communicating with HID (human interface devices) in Windows. 4 | 5 | For an existing project, all you require is the HIDInterface.cs class. This class is a wrapper for the Win32 API calls required for USB operation. It implements simple read/write functions and generates events on asynchronous transmissions. 6 | 7 | The code will build under .NET 2.0, and uses only the APIs available as far back as WinXP. It hsa been tested under XP, Vista and Win7 and Win8 8 | 9 | Also included is a well documented demo of the functionality of the class. This is a console app which implements synchronous and asynchonous operation of the class. 10 | 11 | ## Example 12 | ``` 13 | //Get the details of all connected USB HID devices 14 | HIDDevice.interfaceDetails[] devices = HIDDevice.getConnectedDevices(); 15 | 16 | //Select a device from the available devices 17 | string devicePath = devices[selectedDeviceIndex].devicePath; 18 | 19 | //create a handle to the device by calling the constructor 20 | HIDDevice device = new HIDDevice(devicePath, false); 21 | 22 | //Write a byte array to the device 23 | byte[] writeData = { 0x00, 0x01, 0x02, 0x03, 0x04 }; 24 | device.write(writeData); //Its that easy!! 25 | 26 | //Read a byte array from the device 27 | byte[] readData = device.read(); //again, that easy! 28 | 29 | //close the device to release all handles etc 30 | device.close(); 31 | ``` 32 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("HID_Demo")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("HID_Demo")] 13 | [assembly: AssemblyCopyright("Copyright © 2011")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("66df7691-18d8-4a9a-9fef-a22748ae1d6f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /HID_Demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {DEB4DCCC-72AC-4057-BF73-BF35BC3BD117} 9 | Exe 10 | Properties 11 | HID_Demo 12 | HID_Demo 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | 41 | 3.5 42 | 43 | 44 | 3.5 45 | 46 | 47 | 3.5 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 65 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using HIDInterface; 6 | 7 | namespace HID_Demo 8 | { 9 | class Program 10 | { 11 | /*------------------------------------------------------------------------------------------ 12 | * HID Interface class demo code 13 | * 14 | * This demo code returns the details of all connected HID devices, then selects one of them 15 | * and connects to it. A synchronous read / write operation is performed and the device is 16 | * closed. 17 | * Two methods are included to show the synchronous and asynchronous operations of this software. 18 | * 19 | * The intention of this code is to provide a demonstration which can be run in a debug 20 | * environment, and the sample methods can be cut and pasted into your program 21 | * ----------------------------------------------------------------------------------------*/ 22 | 23 | static void Main(string[] args) 24 | { 25 | HID_demo demo = new HID_demo(); 26 | 27 | //call one or other of these methods to demonstrate each type of operation - sync and async 28 | demo.startAsyncOperation(); 29 | //demo.useSynchronousOperation(); 30 | } 31 | } 32 | 33 | public class HID_demo 34 | { 35 | 36 | // Apologies for the repeated code, however i feel it provides a better demonstration 37 | // of the functionality of this code. 38 | public void useSynchronousOperation() 39 | { 40 | //Get the details of all connected USB HID devices 41 | HIDDevice.interfaceDetails[] devices = HIDDevice.getConnectedDevices(); 42 | 43 | //Arbitrarily select one of the devices which we found in the previous step 44 | //record the details of this device to be used in the class constructor 45 | int selectedDeviceIndex = 0; 46 | ushort VID = devices[selectedDeviceIndex].VID; 47 | ushort PID = devices[selectedDeviceIndex].PID; 48 | int SN = devices[selectedDeviceIndex].serialNumber; 49 | string devicePath = devices[selectedDeviceIndex].devicePath; 50 | 51 | //create a handle to the device by calling the constructor of the HID class 52 | //This can be done using either the VID/PID/Serialnumber, or the device path (string) 53 | //all of these details are available from the HIDDevice.interfaceDetails[] struct array created above 54 | //The "false" boolean in the constructor tells the class we only want synchronous operation 55 | HIDDevice device = new HIDDevice(devicePath, false); 56 | //OR, the normal usage when you know the VID and PID of the device 57 | //HIDDevice device = new HIDDevice(VID, PID, (ushort)SN, false); 58 | 59 | //Write some data to the device (the write method throws an exception if the data is longer than the report length 60 | //specified by the device, this length can be found in the HIDDevice.interfaceDetails struct) 61 | byte[] writeData = { 0x00, 0x01, 0x02, 0x03, 0x04 }; 62 | device.write(writeData); //Its that easy!! 63 | 64 | //Read some data synchronously from the device. This method blocks the calling thread until the data 65 | //is returned. This takes 1-20ms for most HID devices 66 | byte[] readData = device.read(); //again, that easy! 67 | 68 | //close the device to release all handles etc 69 | device.close(); 70 | } 71 | 72 | public void startAsyncOperation() 73 | { 74 | //Get the details of all connected USB HID devices 75 | HIDDevice.interfaceDetails[] devices = HIDDevice.getConnectedDevices(); 76 | 77 | //Arbitrarily select one of the devices which we found in the previous step 78 | //record the details of this device to be used in the class constructor 79 | int selectedDeviceIndex = 0; 80 | ushort VID = devices[selectedDeviceIndex].VID; 81 | ushort PID = devices[selectedDeviceIndex].PID; 82 | int SN = devices[selectedDeviceIndex].serialNumber; 83 | string devicePath = devices[selectedDeviceIndex].devicePath; 84 | 85 | //create a handle to the device by calling the constructor of the HID class 86 | //This can be done using either the VID/PID/Serialnumber, or the device path (string) 87 | //all of these details are available from the HIDDevice.interfaceDetails[] struct array created above 88 | //The "true" boolean in the constructor tells the class we want asynchronous operation this time 89 | HIDDevice device = new HIDDevice(devicePath, true); 90 | //OR, the normal usage when you know the VID and PID of the device 91 | //HIDDevice device = new HIDDevice(VID, PID, (ushort)SN, true); 92 | 93 | //next create the event handler for the incoming reports 94 | device.dataReceived += new HIDDevice.dataReceivedEvent(device_dataReceived); 95 | 96 | //Write some data to the device (the write method throws an exception if the data is longer than the report length 97 | //specified by the device, this length can be found in the HIDDevice.interfaceDetails struct) 98 | //The write method is identical to the synchronous mode of operation 99 | byte[] writeData = { 0x00, 0x01, 0x02, 0x03, 0x04 }; 100 | device.write(writeData); //Its that easy!! 101 | 102 | //Send your program off to do other stuff here, wait for UI events etc 103 | //When a report occurs, the device_dataReceived(byte[] message) method will be called 104 | System.Threading.Thread.Sleep(100); 105 | 106 | //close the device to release all handles etc 107 | device.close(); 108 | } 109 | 110 | //Whenever a report comes in, this method will be called and the data will be available! Like magic... 111 | void device_dataReceived(byte[] message) 112 | { 113 | //Do something with the data here... 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /HIDInterface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Runtime.InteropServices; 5 | using System.Threading; 6 | using Microsoft.Win32.SafeHandles; 7 | using System.IO; 8 | 9 | namespace HIDInterface 10 | { 11 | internal class HIDDevice 12 | { 13 | #region constants 14 | private const int DIGCF_DEFAULT = 0x1; 15 | private const int DIGCF_PRESENT = 0x2; 16 | private const int DIGCF_ALLCLASSES = 0x4; 17 | private const int DIGCF_PROFILE = 0x8; 18 | private const int DIGCF_DEVICEINTERFACE = 0x10; 19 | 20 | private const short FILE_ATTRIBUTE_NORMAL = 0x80; 21 | private const short INVALID_HANDLE_VALUE = -1; 22 | private const uint GENERIC_READ = 0x80000000; 23 | private const uint GENERIC_WRITE = 0x40000000; 24 | private const uint FILE_SHARE_READ = 0x00000001; 25 | private const uint FILE_SHARE_WRITE = 0x00000002; 26 | private const uint CREATE_NEW = 1; 27 | private const uint CREATE_ALWAYS = 2; 28 | private const uint OPEN_EXISTING = 3; 29 | 30 | #endregion 31 | 32 | #region win32_API_declarations 33 | [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] 34 | static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, 35 | IntPtr Enumerator, 36 | IntPtr hwndParent, 37 | uint Flags); 38 | 39 | [DllImport("hid.dll", SetLastError = true)] 40 | private static extern void HidD_GetHidGuid(ref Guid hidGuid); 41 | 42 | [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] 43 | private static extern Boolean SetupDiEnumDeviceInterfaces( 44 | IntPtr hDevInfo, 45 | //ref SP_DEVINFO_DATA devInfo, 46 | IntPtr devInfo, 47 | ref Guid interfaceClassGuid, 48 | UInt32 memberIndex, 49 | ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData 50 | ); 51 | 52 | [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] 53 | private static extern Boolean SetupDiGetDeviceInterfaceDetail( 54 | IntPtr hDevInfo, 55 | ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, 56 | ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData, 57 | UInt32 deviceInterfaceDetailDataSize, 58 | out UInt32 requiredSize, 59 | ref SP_DEVINFO_DATA deviceInfoData 60 | ); 61 | 62 | [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] 63 | private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); 64 | 65 | [DllImport("kernel32.dll", SetLastError = true)] 66 | private static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, 67 | uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, 68 | uint dwFlagsAndAttributes, IntPtr hTemplateFile); 69 | 70 | [DllImport("kernel32.dll", SetLastError = true)] 71 | private static extern bool ReadFile(SafeFileHandle hFile, byte[] lpBuffer, 72 | uint nNumberOfBytesToRead, ref uint lpNumberOfBytesRead, IntPtr lpOverlapped); 73 | 74 | [DllImport("kernel32.dll", SetLastError = true)] 75 | private static extern bool WriteFile(SafeFileHandle hFile, byte[] lpBuffer, 76 | uint nNumberOfBytesToWrite, ref uint lpNumberOfBytesWritten, IntPtr lpOverlapped); 77 | 78 | [DllImport("hid.dll", SetLastError = true)] 79 | private static extern bool HidD_GetPreparsedData( 80 | SafeFileHandle hObject, 81 | ref IntPtr PreparsedData); 82 | 83 | [DllImport("hid.dll", SetLastError = true)] 84 | private static extern Boolean HidD_FreePreparsedData(ref IntPtr PreparsedData); 85 | 86 | [DllImport("hid.dll", SetLastError = true)] 87 | private static extern int HidP_GetCaps( 88 | IntPtr pPHIDP_PREPARSED_DATA, // IN PHIDP_PREPARSED_DATA PreparsedData, 89 | ref HIDP_CAPS myPHIDP_CAPS); // OUT PHIDP_CAPS Capabilities 90 | 91 | [DllImport("hid.dll", SetLastError = true)] 92 | private static extern Boolean HidD_GetAttributes(SafeFileHandle hObject, ref HIDD_ATTRIBUTES Attributes); 93 | 94 | [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)] 95 | private static extern bool HidD_GetFeature( 96 | IntPtr hDevice, 97 | IntPtr hReportBuffer, 98 | uint ReportBufferLength); 99 | 100 | [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)] 101 | private static extern bool HidD_SetFeature( 102 | IntPtr hDevice, 103 | IntPtr ReportBuffer, 104 | uint ReportBufferLength); 105 | 106 | [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)] 107 | private static extern bool HidD_GetProductString( 108 | SafeFileHandle hDevice, 109 | IntPtr Buffer, 110 | uint BufferLength); 111 | 112 | [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)] 113 | private static extern bool HidD_GetSerialNumberString( 114 | SafeFileHandle hDevice, 115 | IntPtr Buffer, 116 | uint BufferLength); 117 | 118 | [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)] 119 | private static extern Boolean HidD_GetManufacturerString( 120 | SafeFileHandle hDevice, 121 | IntPtr Buffer, 122 | uint BufferLength); 123 | 124 | #endregion 125 | 126 | #region structs 127 | 128 | public struct interfaceDetails 129 | { 130 | public string manufacturer; 131 | public string product; 132 | public int serialNumber; 133 | public ushort VID; 134 | public ushort PID; 135 | public string devicePath; 136 | public int IN_reportByteLength; 137 | public int OUT_reportByteLength; 138 | public ushort versionNumber; 139 | } 140 | 141 | // HIDP_CAPS 142 | [StructLayout(LayoutKind.Sequential)] 143 | private struct HIDP_CAPS 144 | { 145 | public System.UInt16 Usage; // USHORT 146 | public System.UInt16 UsagePage; // USHORT 147 | public System.UInt16 InputReportByteLength; 148 | public System.UInt16 OutputReportByteLength; 149 | public System.UInt16 FeatureReportByteLength; 150 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)] 151 | public System.UInt16[] Reserved; // USHORT Reserved[17]; 152 | public System.UInt16 NumberLinkCollectionNodes; 153 | public System.UInt16 NumberInputButtonCaps; 154 | public System.UInt16 NumberInputValueCaps; 155 | public System.UInt16 NumberInputDataIndices; 156 | public System.UInt16 NumberOutputButtonCaps; 157 | public System.UInt16 NumberOutputValueCaps; 158 | public System.UInt16 NumberOutputDataIndices; 159 | public System.UInt16 NumberFeatureButtonCaps; 160 | public System.UInt16 NumberFeatureValueCaps; 161 | public System.UInt16 NumberFeatureDataIndices; 162 | } 163 | 164 | [StructLayout(LayoutKind.Sequential)] 165 | private struct SP_DEVINFO_DATA 166 | { 167 | public uint cbSize; 168 | public Guid ClassGuid; 169 | public uint DevInst; 170 | public IntPtr Reserved; 171 | } 172 | [StructLayout(LayoutKind.Sequential)] 173 | private struct SP_DEVICE_INTERFACE_DATA 174 | { 175 | public uint cbSize; 176 | public Guid InterfaceClassGuid; 177 | public uint Flags; 178 | public IntPtr Reserved; 179 | } 180 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 181 | private struct SP_DEVICE_INTERFACE_DETAIL_DATA 182 | { 183 | public int cbSize; 184 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] 185 | public string DevicePath; 186 | } 187 | 188 | [StructLayout(LayoutKind.Sequential)] 189 | private struct HIDD_ATTRIBUTES 190 | { 191 | public Int32 Size; 192 | public Int16 VendorID; 193 | public Int16 ProductID; 194 | public Int16 VersionNumber; 195 | } 196 | 197 | [StructLayout(LayoutKind.Sequential)] 198 | private struct COMMTIMEOUTS 199 | { 200 | public UInt32 ReadIntervalTimeout; 201 | public UInt32 ReadTotalTimeoutMultiplier; 202 | public UInt32 ReadTotalTimeoutConstant; 203 | public UInt32 WriteTotalTimeoutMultiplier; 204 | public UInt32 WriteTotalTimeoutConstant; 205 | } 206 | 207 | #endregion 208 | 209 | #region globals 210 | public bool deviceConnected { get; set; } 211 | private SafeFileHandle handle_read; 212 | private SafeFileHandle handle_write; 213 | private FileStream FS_read; 214 | private FileStream FS_write; 215 | private HIDP_CAPS capabilities; 216 | public interfaceDetails productInfo; 217 | public event dataReceivedEvent dataReceived; //The calling class can subscribe to this event 218 | public delegate void dataReceivedEvent(byte[] message); 219 | public byte[] readData; 220 | private bool useAsyncReads; 221 | 222 | #endregion 223 | 224 | #region static_methods 225 | 226 | public static interfaceDetails[] getConnectedDevices() 227 | { 228 | interfaceDetails[] devices = new interfaceDetails[0]; 229 | 230 | //Create structs to hold interface information 231 | SP_DEVINFO_DATA devInfo = new SP_DEVINFO_DATA(); 232 | SP_DEVICE_INTERFACE_DATA devIface = new SP_DEVICE_INTERFACE_DATA(); 233 | devInfo.cbSize = (uint)Marshal.SizeOf(devInfo); 234 | devIface.cbSize = (uint)(Marshal.SizeOf(devIface)); 235 | 236 | Guid G = new Guid(); 237 | HidD_GetHidGuid(ref G); //Get the guid of the HID device class 238 | 239 | IntPtr i = SetupDiGetClassDevs(ref G, IntPtr.Zero, IntPtr.Zero, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); 240 | 241 | //Loop through all available entries in the device list, until false 242 | SP_DEVICE_INTERFACE_DETAIL_DATA didd = new SP_DEVICE_INTERFACE_DETAIL_DATA(); 243 | if (IntPtr.Size == 8) // for 64 bit operating systems 244 | didd.cbSize = 8; 245 | else 246 | didd.cbSize = 4 + Marshal.SystemDefaultCharSize; // for 32 bit systems 247 | 248 | int j = -1; 249 | bool b = true; 250 | int error; 251 | SafeFileHandle tempHandle; 252 | 253 | while (b) 254 | { 255 | j++; 256 | 257 | b = SetupDiEnumDeviceInterfaces(i, IntPtr.Zero, ref G, (uint)j, ref devIface); 258 | error = Marshal.GetLastWin32Error(); 259 | if (b == false) 260 | break; 261 | 262 | uint requiredSize = 0; 263 | bool b1 = SetupDiGetDeviceInterfaceDetail(i, ref devIface, ref didd, 256, out requiredSize, ref devInfo); 264 | string devicePath = didd.DevicePath; 265 | 266 | //create file handles using CT_CreateFile 267 | tempHandle = CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 268 | IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); 269 | 270 | //get capabilites - use getPreParsedData, and getCaps 271 | //store the reportlengths 272 | IntPtr ptrToPreParsedData = new IntPtr(); 273 | bool ppdSucsess = HidD_GetPreparsedData(tempHandle, ref ptrToPreParsedData); 274 | if (ppdSucsess == false) 275 | continue; 276 | 277 | HIDP_CAPS capabilities = new HIDP_CAPS(); 278 | int hidCapsSucsess = HidP_GetCaps(ptrToPreParsedData, ref capabilities); 279 | 280 | HIDD_ATTRIBUTES attributes = new HIDD_ATTRIBUTES(); 281 | bool hidAttribSucsess = HidD_GetAttributes(tempHandle, ref attributes); 282 | 283 | string productName = ""; 284 | string SN = ""; 285 | string manfString = ""; 286 | IntPtr buffer = Marshal.AllocHGlobal(126);//max alloc for string; 287 | if (HidD_GetProductString(tempHandle, buffer, 126)) productName = Marshal.PtrToStringAuto(buffer); 288 | if (HidD_GetSerialNumberString(tempHandle, buffer, 126)) SN = Marshal.PtrToStringAuto(buffer); 289 | if (HidD_GetManufacturerString(tempHandle, buffer, 126)) manfString = Marshal.PtrToStringAuto(buffer); 290 | Marshal.FreeHGlobal(buffer); 291 | 292 | //Call freePreParsedData to release some stuff 293 | HidD_FreePreparsedData(ref ptrToPreParsedData); 294 | 295 | //If connection was sucsessful, record the values in a global struct 296 | interfaceDetails productInfo = new interfaceDetails(); 297 | productInfo.devicePath = devicePath; 298 | productInfo.manufacturer = manfString; 299 | productInfo.product = productName; 300 | productInfo.PID = (ushort)attributes.ProductID; 301 | productInfo.VID = (ushort)attributes.VendorID; 302 | productInfo.versionNumber = (ushort)attributes.VersionNumber; 303 | productInfo.IN_reportByteLength = (int)capabilities.InputReportByteLength; 304 | productInfo.OUT_reportByteLength = (int)capabilities.OutputReportByteLength; 305 | 306 | if (stringIsInteger(SN)) 307 | productInfo.serialNumber = Convert.ToInt32(SN); //Check that serial number is actually a number 308 | 309 | int newSize = devices.Length + 1; 310 | Array.Resize(ref devices, newSize); 311 | devices[newSize - 1] = productInfo; 312 | } 313 | SetupDiDestroyDeviceInfoList(i); 314 | 315 | return devices; 316 | } 317 | 318 | #endregion 319 | 320 | #region constructors 321 | /// 322 | /// Creates an object to handle read/write functionality for a USB HID device 323 | /// Uses one filestream for each of read/write to allow for a write to occur during a blocking 324 | /// asnychronous read 325 | /// 326 | /// The vendor ID of the USB device to connect to 327 | /// The product ID of the USB device to connect to 328 | /// The serial number of the USB device to connect to 329 | /// True - Read the device and generate events on data being available 330 | public HIDDevice(ushort VID, ushort PID, ushort serialNumber, bool useAsyncReads) 331 | { 332 | interfaceDetails[] devices = getConnectedDevices(); 333 | 334 | //loop through all connected devices to find one with the correct details 335 | for (int i = 0; i < devices.Length; i++) 336 | { 337 | if ((devices[i].VID == VID) && (devices[i].PID == PID) && (devices[i].serialNumber == (int)serialNumber)) 338 | initDevice(devices[i].devicePath, useAsyncReads); 339 | } 340 | 341 | if (!deviceConnected) 342 | { 343 | string hexVID = numToHexString(VID); 344 | string hexPID = numToHexString(PID); 345 | throw new Exception("Device with VID: 0x" + hexVID + " PID: 0x" + hexPID + " SerialNumber: " + serialNumber.ToString() + " could not be found"); 346 | } 347 | } 348 | 349 | /// 350 | /// Creates an object to handle read/write functionality for a USB HID device 351 | /// Uses one filestream for each of read/write to allow for a write to occur during a blocking 352 | /// asnychronous read 353 | /// 354 | /// The USB device path - from getConnectedDevices 355 | /// True - Read the device and generate events on data being available 356 | public HIDDevice(string devicePath, bool useAsyncReads) 357 | { 358 | initDevice(devicePath, useAsyncReads); 359 | 360 | if (!deviceConnected) 361 | { 362 | throw new Exception("Device could not be found"); 363 | } 364 | } 365 | #endregion 366 | 367 | #region functions 368 | private void initDevice(string devicePath, bool useAsyncReads) 369 | { 370 | deviceConnected = false; 371 | 372 | //create file handles using CT_CreateFile 373 | handle_read = CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 374 | IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); 375 | 376 | handle_write = CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 377 | IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); 378 | 379 | //get capabilites - use getPreParsedData, and getCaps 380 | //store the reportlengths 381 | IntPtr ptrToPreParsedData = new IntPtr(); 382 | bool ppdSucsess = HidD_GetPreparsedData(handle_read, ref ptrToPreParsedData); 383 | 384 | capabilities = new HIDP_CAPS(); 385 | int hidCapsSucsess = HidP_GetCaps(ptrToPreParsedData, ref capabilities); 386 | 387 | HIDD_ATTRIBUTES attributes = new HIDD_ATTRIBUTES(); 388 | bool hidAttribSucsess = HidD_GetAttributes(handle_read, ref attributes); 389 | 390 | string productName = ""; 391 | string SN = ""; 392 | string manfString = ""; 393 | IntPtr buffer = Marshal.AllocHGlobal(126);//max alloc for string; 394 | if (HidD_GetProductString(handle_read, buffer, 126)) productName = Marshal.PtrToStringAuto(buffer); 395 | if (HidD_GetSerialNumberString(handle_read, buffer, 126)) SN = Marshal.PtrToStringAuto(buffer); 396 | if (HidD_GetManufacturerString(handle_read, buffer, 126)) manfString = Marshal.PtrToStringAuto(buffer); 397 | Marshal.FreeHGlobal(buffer); 398 | 399 | //Call freePreParsedData to release some stuff 400 | HidD_FreePreparsedData(ref ptrToPreParsedData); 401 | //SetupDiDestroyDeviceInfoList(i); 402 | 403 | if (handle_read.IsInvalid) 404 | return; 405 | 406 | deviceConnected = true; 407 | 408 | //If connection was sucsessful, record the values in a global struct 409 | productInfo = new interfaceDetails(); 410 | productInfo.devicePath = devicePath; 411 | productInfo.manufacturer = manfString; 412 | productInfo.product = productName; 413 | productInfo.serialNumber = Convert.ToInt32(SN); 414 | productInfo.PID = (ushort)attributes.ProductID; 415 | productInfo.VID = (ushort)attributes.VendorID; 416 | productInfo.versionNumber = (ushort)attributes.VersionNumber; 417 | productInfo.IN_reportByteLength = (int)capabilities.InputReportByteLength; 418 | productInfo.OUT_reportByteLength = (int)capabilities.OutputReportByteLength; 419 | 420 | //use a filestream object to bring this stuff into .NET 421 | FS_read = new FileStream(handle_read, FileAccess.ReadWrite, capabilities.OutputReportByteLength, false); 422 | FS_write = new FileStream(handle_write, FileAccess.ReadWrite, capabilities.InputReportByteLength, false); 423 | 424 | this.useAsyncReads = useAsyncReads; 425 | if (useAsyncReads) 426 | readAsync(); 427 | } 428 | 429 | public void close() 430 | { 431 | if (FS_read != null) 432 | FS_read.Close(); 433 | if (FS_write != null) 434 | FS_write.Close(); 435 | 436 | if ((handle_read != null) && (!(handle_read.IsInvalid))) 437 | handle_read.Close(); 438 | if ((handle_write != null) && (!(handle_write.IsInvalid))) 439 | handle_write.Close(); 440 | 441 | this.deviceConnected = false; 442 | } 443 | 444 | public void write(byte[] data) 445 | { 446 | if (data.Length > capabilities.OutputReportByteLength) 447 | throw new Exception("Output report must not exceed " + (capabilities.OutputReportByteLength - 1).ToString() + " bytes"); 448 | 449 | //uint numBytesWritten = 0; 450 | byte[] packet = new byte[capabilities.OutputReportByteLength]; 451 | Array.Copy(data, 0, packet, 1, data.Length); //start at 1, as the first byte must be zero for HID report 452 | packet[0] = 0; 453 | 454 | if (FS_write.CanWrite) 455 | FS_write.Write(packet, 0, packet.Length); 456 | else 457 | throw new Exception("Filestream unable to write"); 458 | } 459 | 460 | //This read function will be used with asychronous operation, called by the constructor if async reads are used 461 | private void readAsync() 462 | { 463 | readData = new byte[capabilities.InputReportByteLength]; 464 | if (FS_read.CanRead) 465 | FS_read.BeginRead(readData, 0, readData.Length, new AsyncCallback(GetInputReportData), readData); 466 | else 467 | throw new Exception("Device is unable to read"); 468 | } 469 | 470 | private void GetInputReportData(IAsyncResult ar) 471 | { 472 | FS_read.EndRead(ar); //must call an endread before starting another one 473 | //TODO handle exception with PCB is reaet 474 | 475 | //Reset the read thread to read the next report 476 | if (FS_read.CanRead) 477 | FS_read.BeginRead(readData, 0, readData.Length, new AsyncCallback(GetInputReportData), readData); 478 | else 479 | throw new Exception("Device is unable to read"); 480 | 481 | dataReceived(readData); //triggers the event to be heard by the calling class 482 | } 483 | 484 | /// 485 | /// This read function is for normal synchronous reads 486 | /// 487 | /// 488 | public byte[] read() 489 | { 490 | if (useAsyncReads == true) 491 | throw new Exception("A synchonous read cannot be executed when operating in async mode"); 492 | 493 | //Call readFile 494 | byte[] readBuf = new byte[capabilities.InputReportByteLength]; 495 | FS_read.Read(readBuf, 0, readBuf.Length); 496 | return readBuf; 497 | } 498 | #endregion 499 | 500 | #region utilities 501 | 502 | public static bool stringIsInteger(string val) 503 | { 504 | Double result; 505 | return Double.TryParse(val, System.Globalization.NumberStyles.Integer, 506 | System.Globalization.CultureInfo.CurrentCulture, out result); 507 | } 508 | 509 | public static string numToHexString(ushort num) 510 | { 511 | return String.Format("{0:X}", num); 512 | } 513 | 514 | #endregion 515 | } 516 | } --------------------------------------------------------------------------------