Migrate WinUSBNet code to a standalone project dependency

This commit is contained in:
Gustave Monce
2025-11-01 09:28:08 +01:00
parent 1cc7a5cee4
commit 058cde0d91
25 changed files with 659 additions and 15 deletions
+48
View File
@@ -0,0 +1,48 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace MadWizard.WinUSBNet.API
{
/// <summary>
/// Exception used internally to catch Win32 API errors. This exception should
/// not be thrown to the library's caller.
/// </summary>
internal class APIException : Exception
{
public APIException(string message) :
base(message)
{
}
public APIException(string message, Exception innerException) : base(message, innerException)
{
}
public APIException() : base()
{
}
public static APIException Win32(string message)
{
return Win32(message, Marshal.GetLastWin32Error());
// TEST!!
// int ErrorCode = Marshal.GetLastWin32Error();
// if ((ErrorCode & 0xffff) == 0x1f)
// ErrorCode = ErrorCode; // Break here
// return APIException.Win32(message, ErrorCode);
}
public static APIException Win32(string message, int errorCode)
{
return new APIException(message, new Win32Exception(errorCode));
}
}
}
+21
View File
@@ -0,0 +1,21 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
namespace MadWizard.WinUSBNet.API
{
internal struct DeviceDetails
{
public string DevicePath;
public string Manufacturer;
public string DeviceDescription;
public ushort VID;
public ushort PID;
// Heathcliff74
public string BusName;
}
}
+372
View File
@@ -0,0 +1,372 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
/* NOTE: Parts of the code in this file are based on the work of Jan Axelson
* See http://www.lvr.com/winusb.htm for more information
*/
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace MadWizard.WinUSBNet.API
{
/// <summary>
/// Routines for detecting devices and receiving device notifications.
/// </summary>
internal static partial class DeviceManagement
{
// Get device name from notification message.
// Also checks checkGuid with the GUID from the message to check the notification
// is for a relevant device. Other messages might be received.
public static string GetNotifyMessageDeviceName(IntPtr pDevBroadcastHeader, Guid checkGuid)
{
int stringSize;
DEV_BROADCAST_DEVICEINTERFACE_1 devBroadcastDeviceInterface = new();
DEV_BROADCAST_HDR devBroadcastHeader = new();
// The LParam parameter of Message is a pointer to a DEV_BROADCAST_HDR structure.
Marshal.PtrToStructure(pDevBroadcastHeader, devBroadcastHeader);
if (devBroadcastHeader.dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
// The dbch_devicetype parameter indicates that the event applies to a device interface.
// So the structure in LParam is actually a DEV_BROADCAST_INTERFACE structure,
// which begins with a DEV_BROADCAST_HDR.
// Obtain the number of characters in dbch_name by subtracting the 32 bytes
// in the strucutre that are not part of dbch_name and dividing by 2 because there are
// 2 bytes per character.
stringSize = Convert.ToInt32((devBroadcastHeader.dbch_size - 32) / 2);
// The dbcc_name parameter of devBroadcastDeviceInterface contains the device name.
// Trim dbcc_name to match the size of the String.
devBroadcastDeviceInterface.dbcc_name = new char[stringSize + 1];
// Marshal data from the unmanaged block pointed to by m.LParam
// to the managed object devBroadcastDeviceInterface.
Marshal.PtrToStructure(pDevBroadcastHeader, devBroadcastDeviceInterface);
// Check if message is for the GUID
if (devBroadcastDeviceInterface.dbcc_classguid != checkGuid)
{
return null;
}
// Store the device name in a String.
return (string)(new(devBroadcastDeviceInterface.dbcc_name, 0, stringSize));
}
else if (devBroadcastHeader.dbch_devicetype == DBT_DEVTYP_VOLUME)
{
DEV_BROADCAST_VOLUME vol = new();
Marshal.PtrToStructure(pDevBroadcastHeader, vol);
Int32 Mask = vol.dbcv_unitmask;
Int32 DriveInt = Convert.ToInt32('A') - 1;
do
{
Mask /= 2;
DriveInt++;
}
while (Mask > 0);
return @"\\.\" + Convert.ToChar(DriveInt) + ":";
}
return null;
}
private static byte[] GetProperty(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, SPDRP property, out int regType)
{
if (!SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, property, IntPtr.Zero, IntPtr.Zero, 0, out uint requiredSize) && Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER)
{
throw APIException.Win32("Failed to get buffer size for device registry property.");
}
byte[] buffer = new byte[requiredSize];
if (!SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, property, out regType, buffer, (uint)buffer.Length, out requiredSize))
{
throw APIException.Win32("Failed to get device registry property.");
}
return buffer;
}
//DEFINE_DEVPROPKEY(DEVPKEY_Device_BusReportedDeviceDesc, 0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2, 4); // DEVPROP_TYPE_STRING
//DEFINE_DEVPROPKEY(DEVPKEY_Device_ContainerId, 0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c, 2); // DEVPROP_TYPE_GUID
//DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
//DEFINE_DEVPROPKEY(DEVPKEY_DeviceDisplay_Category, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 0x5a); // DEVPROP_TYPE_STRING_LIST
//DEFINE_DEVPROPKEY(DEVPKEY_Device_LocationInfo, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 15); // DEVPROP_TYPE_STRING
//DEFINE_DEVPROPKEY(DEVPKEY_Device_Manufacturer, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); // DEVPROP_TYPE_STRING
//DEFINE_DEVPROPKEY(DEVPKEY_Device_SecuritySDS, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 26); // DEVPROP_TYPE_SECURITY_DESCRIPTOR_STRING
// Heathcliff74
private static byte[] GetProperty(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, DEVPROPKEY property, out uint propertyType)
{
if (!SetupDiGetDeviceProperty(deviceInfoSet, ref deviceInfoData, ref property, out propertyType, null, 0, out int requiredSize, 0) && Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER)
{
throw APIException.Win32("Failed to get buffer size for device registry property.");
}
byte[] buffer = new byte[requiredSize];
if (!SetupDiGetDeviceProperty(deviceInfoSet, ref deviceInfoData, ref property, out propertyType, buffer, buffer.Length, out requiredSize, 0))
{
throw APIException.Win32("Failed to get device registry property.");
}
return buffer;
}
// todo: is the queried data always available, or should we check ERROR_INVALID_DATA?
private static string GetStringProperty(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, SPDRP property)
{
byte[] buffer = GetProperty(deviceInfoSet, deviceInfoData, property, out int regType);
if (regType != (int)RegTypes.REG_SZ)
{
throw new APIException("Invalid registry type returned for device property.");
}
// sizof(char), 2 bytes, are removed to leave out the string terminator
return System.Text.Encoding.Unicode.GetString(buffer, 0, buffer.Length - sizeof(char));
}
// Heathcliff74
// todo: is the queried data always available, or should we check ERROR_INVALID_DATA?
private static string GetStringProperty(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, DEVPROPKEY property)
{
byte[] buffer = GetProperty(deviceInfoSet, deviceInfoData, property, out uint propertyType);
if (propertyType != 0x00000012) // DEVPROP_TYPE_STRING
{
throw new APIException("Invalid registry type returned for device property.");
}
// sizof(char), 2 bytes, are removed to leave out the string terminator
return System.Text.Encoding.Unicode.GetString(buffer, 0, buffer.Length - sizeof(char));
}
private static string[] GetMultiStringProperty(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, SPDRP property)
{
byte[] buffer = GetProperty(deviceInfoSet, deviceInfoData, property, out int regType);
if (regType != (int)RegTypes.REG_MULTI_SZ)
{
throw new APIException("Invalid registry type returned for device property.");
}
string fullString = System.Text.Encoding.Unicode.GetString(buffer);
return fullString.Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
}
private static DeviceDetails GetDeviceDetails(string devicePath, IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData)
{
DeviceDetails details = new()
{
DevicePath = devicePath,
DeviceDescription = GetStringProperty(deviceInfoSet, deviceInfoData, SPDRP.SPDRP_DEVICEDESC),
Manufacturer = GetStringProperty(deviceInfoSet, deviceInfoData, SPDRP.SPDRP_MFG),
// Heathcliff74
BusName = ""
};
try
{
details.BusName = GetStringProperty(deviceInfoSet, deviceInfoData, new DEVPROPKEY(new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), 4));
}
catch { }
string[] hardwareIDs = GetMultiStringProperty(deviceInfoSet, deviceInfoData, SPDRP.SPDRP_HARDWAREID);
Regex regex = new("^USB\\\\VID_([0-9A-F]{4})&PID_([0-9A-F]{4})", RegexOptions.IgnoreCase);
foreach (string hardwareID in hardwareIDs)
{
Match match = regex.Match(hardwareID);
if (match.Success)
{
details.VID = ushort.Parse(match.Groups[1].Value, System.Globalization.NumberStyles.AllowHexSpecifier);
details.PID = ushort.Parse(match.Groups[2].Value, System.Globalization.NumberStyles.AllowHexSpecifier);
break;
}
}
// Heathcliff74
// Not all devices have numeric VID and PID
//
// if (!foundVidPid)
// throw new APIException("Failed to find VID and PID for USB device. No hardware ID could be parsed.");
return details;
}
public static DeviceDetails[] FindDevicesFromGuid(Guid guid)
{
IntPtr deviceInfoSet = IntPtr.Zero;
List<DeviceDetails> deviceList = [];
try
{
deviceInfoSet = SetupDiGetClassDevs(ref guid, IntPtr.Zero, IntPtr.Zero,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (deviceInfoSet == FileIO.INVALID_HANDLE_VALUE)
{
throw APIException.Win32("Failed to enumerate devices.");
}
int memberIndex = 0;
while (true)
{
// Begin with 0 and increment through the device information set until
// no more devices are available.
SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new();
// The cbSize element of the deviceInterfaceData structure must be set to
// the structure's size in bytes.
// The size is 28 bytes for 32-bit code and 32 bytes for 64-bit code.
deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData);
bool success = SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref guid, memberIndex, ref deviceInterfaceData);
// Find out if a device information set was retrieved.
if (!success)
{
int lastError = Marshal.GetLastWin32Error();
if (lastError == ERROR_NO_MORE_ITEMS)
{
break;
}
throw APIException.Win32("Failed to get device interface.");
}
// A device is present.
int bufferSize = 0;
success = SetupDiGetDeviceInterfaceDetail
(deviceInfoSet,
ref deviceInterfaceData,
IntPtr.Zero,
0,
ref bufferSize,
IntPtr.Zero);
if (!success && Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER)
{
throw APIException.Win32("Failed to get interface details buffer size.");
}
IntPtr detailDataBuffer = IntPtr.Zero;
try
{
// Allocate memory for the SP_DEVICE_INTERFACE_DETAIL_DATA structure using the returned buffer size.
detailDataBuffer = Marshal.AllocHGlobal(bufferSize);
// Store cbSize in the first bytes of the array. The number of bytes varies with 32- and 64-bit systems.
Marshal.WriteInt32(detailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);
// Call SetupDiGetDeviceInterfaceDetail again.
// This time, pass a pointer to DetailDataBuffer
// and the returned required buffer size.
// build a DevInfo Data structure
SP_DEVINFO_DATA da = new();
da.cbSize = Marshal.SizeOf(da);
success = SetupDiGetDeviceInterfaceDetail
(deviceInfoSet,
ref deviceInterfaceData,
detailDataBuffer,
bufferSize,
ref bufferSize,
ref da);
if (!success)
{
throw APIException.Win32("Failed to get device interface details.");
}
// Skip over cbsize (4 bytes) to get the address of the devicePathName.
IntPtr pDevicePathName = new(detailDataBuffer.ToInt64() + 4);
string pathName = Marshal.PtrToStringUni(pDevicePathName);
// Get the String containing the devicePathName.
DeviceDetails details = GetDeviceDetails(pathName, deviceInfoSet, da);
deviceList.Add(details);
}
finally
{
if (detailDataBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(detailDataBuffer);
detailDataBuffer = IntPtr.Zero;
}
}
memberIndex++;
}
}
finally
{
if (deviceInfoSet != IntPtr.Zero && deviceInfoSet != FileIO.INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
}
return [.. deviceList];
}
public static void RegisterForDeviceNotifications(IntPtr controlHandle, Guid classGuid, ref IntPtr deviceNotificationHandle)
{
DEV_BROADCAST_DEVICEINTERFACE devBroadcastDeviceInterface = new();
IntPtr devBroadcastDeviceInterfaceBuffer = IntPtr.Zero;
try
{
int size = Marshal.SizeOf(devBroadcastDeviceInterface);
devBroadcastDeviceInterface.dbcc_size = size;
devBroadcastDeviceInterface.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
devBroadcastDeviceInterface.dbcc_reserved = 0;
devBroadcastDeviceInterface.dbcc_classguid = classGuid;
devBroadcastDeviceInterfaceBuffer = Marshal.AllocHGlobal(size);
// Copy the DEV_BROADCAST_DEVICEINTERFACE structure to the buffer.
// Set fDeleteOld True to prevent memory leaks.
Marshal.StructureToPtr(devBroadcastDeviceInterface, devBroadcastDeviceInterfaceBuffer, true);
deviceNotificationHandle = RegisterDeviceNotification(controlHandle, devBroadcastDeviceInterfaceBuffer, DEVICE_NOTIFY_WINDOW_HANDLE);
if (deviceNotificationHandle == IntPtr.Zero)
{
throw APIException.Win32("Failed to register device notification");
}
// Marshal data from the unmanaged block devBroadcastDeviceInterfaceBuffer to
// the managed object devBroadcastDeviceInterface
Marshal.PtrToStructure(devBroadcastDeviceInterfaceBuffer, devBroadcastDeviceInterface);
}
finally
{
// Free the memory allocated previously by AllocHGlobal.
if (devBroadcastDeviceInterfaceBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(devBroadcastDeviceInterfaceBuffer);
}
}
}
public static void StopDeviceDeviceNotifications(IntPtr deviceNotificationHandle)
{
if (!UnregisterDeviceNotification(deviceNotificationHandle))
{
throw APIException.Win32("Failed to unregister device notification");
}
}
}
}
+180
View File
@@ -0,0 +1,180 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
/* NOTE: Parts of the code in this file are based on the work of Jan Axelson
* See http://www.lvr.com/winusb.htm for more information
*/
using System;
using System.Runtime.InteropServices;
namespace MadWizard.WinUSBNet.API
{
/// <summary>
/// API declarations relating to device management (SetupDixxx and
/// RegisterDeviceNotification functions).
/// </summary>
internal static partial class DeviceManagement
{
// from dbt.h
internal const Int32 DBT_DEVICEARRIVAL = 0X8000;
internal const Int32 DBT_DEVICEREMOVECOMPLETE = 0X8004;
internal const Int32 DBT_QUERYCHANGECONFIG = 0x0017;
private const Int32 DBT_DEVTYP_DEVICEINTERFACE = 5;
private const Int32 DBT_DEVTYP_VOLUME = 2; // drive type is logical volume
private const Int32 DBT_DEVTYP_HANDLE = 6;
private const Int32 DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4;
private const Int32 DEVICE_NOTIFY_SERVICE_HANDLE = 1;
private const Int32 DEVICE_NOTIFY_WINDOW_HANDLE = 0;
internal const Int32 WM_DEVICECHANGE = 0X219;
internal const Int32 DBT_DEVNODES_CHANGED = 7;
// from setupapi.h
private const Int32 DIGCF_PRESENT = 2;
private const Int32 DIGCF_DEVICEINTERFACE = 0X10;
// Two declarations for the DEV_BROADCAST_DEVICEINTERFACE structure.
// Use this one in the call to RegisterDeviceNotification() and
// in checking dbch_devicetype in a DEV_BROADCAST_HDR structure:
[StructLayout(LayoutKind.Sequential)]
private class DEV_BROADCAST_DEVICEINTERFACE
{
internal Int32 dbcc_size;
internal Int32 dbcc_devicetype;
internal Int32 dbcc_reserved;
internal Guid dbcc_classguid;
internal Int16 dbcc_name;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private class DEV_BROADCAST_DEVICEINTERFACE_1
{
internal Int32 dbcc_size;
internal Int32 dbcc_devicetype;
internal Int32 dbcc_reserved;
internal Guid dbcc_classguid;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
internal Char[] dbcc_name;
}
[StructLayout(LayoutKind.Sequential)]
public class DEV_BROADCAST_VOLUME
{
public Int32 dbcv_size;
public Int32 dbcv_devicetype;
public Int32 dbcv_reserved;
public Int32 dbcv_unitmask;
}
[StructLayout(LayoutKind.Sequential)]
private class DEV_BROADCAST_HDR
{
internal Int32 dbch_size;
internal Int32 dbch_devicetype;
internal Int32 dbch_reserved;
}
private struct SP_DEVICE_INTERFACE_DATA
{
internal Int32 cbSize;
internal Guid InterfaceClassGuid;
internal Int32 Flags;
internal IntPtr Reserved;
}
private struct SP_DEVINFO_DATA
{
internal Int32 cbSize;
internal Guid ClassGuid;
internal Int32 DevInst;
internal IntPtr Reserved;
}
// from pinvoke.net
private enum SPDRP : uint
{
SPDRP_DEVICEDESC = 0x00000000,
SPDRP_HARDWAREID = 0x00000001,
SPDRP_COMPATIBLEIDS = 0x00000002,
SPDRP_NTDEVICEPATHS = 0x00000003,
SPDRP_SERVICE = 0x00000004,
SPDRP_CONFIGURATION = 0x00000005,
SPDRP_CONFIGURATIONVECTOR = 0x00000006,
SPDRP_CLASS = 0x00000007,
SPDRP_CLASSGUID = 0x00000008,
SPDRP_DRIVER = 0x00000009,
SPDRP_CONFIGFLAGS = 0x0000000A,
SPDRP_MFG = 0x0000000B,
SPDRP_FRIENDLYNAME = 0x0000000C,
SPDRP_LOCATION_INFORMATION = 0x0000000D,
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E,
SPDRP_CAPABILITIES = 0x0000000F,
SPDRP_UI_NUMBER = 0x00000010,
SPDRP_UPPERFILTERS = 0x00000011,
SPDRP_LOWERFILTERS = 0x00000012,
SPDRP_MAXIMUM_PROPERTY = 0x00000013,
SPDRP_ENUMERATOR_NAME = 0x16,
};
// Device Property
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DEVPROPKEY
{
public DEVPROPKEY(Guid ifmtid, UInt32 ipid)
{ fmtid = ifmtid; pid = ipid; }
public Guid fmtid;
public UInt32 pid;
}
private enum RegTypes : int
{
// incomplete list, these are just the ones used.
REG_SZ = 1,
REG_MULTI_SZ = 7
}
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr NotificationFilter, Int32 Flags);
//[DllImport("setupapi.dll", SetLastError = true)]
//internal static extern Int32 SetupDiCreateDeviceInfoList(ref System.Guid ClassGuid, Int32 hwndParent);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern Int32 SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, ref Guid InterfaceClassGuid, Int32 MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool SetupDiGetDeviceRegistryProperty(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, SPDRP Property, out int PropertyRegDataType, byte[] PropertyBuffer, uint PropertyBufferSize, out UInt32 RequiredSize);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool SetupDiGetDeviceRegistryProperty(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, SPDRP Property, IntPtr PropertyRegDataType, IntPtr PropertyBuffer, uint PropertyBufferSize, out UInt32 RequiredSize);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern unsafe bool SetupDiGetDeviceProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref DEVPROPKEY propertyKey, out UInt32 propertyType, byte[] propertyBuffer, Int32 propertyBufferSize, out int requiredSize, UInt32 flags);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, Int32 Flags);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, IntPtr DeviceInfoData);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnregisterDeviceNotification(IntPtr Handle);
private const int ERROR_NO_MORE_ITEMS = 259;
private const int ERROR_INSUFFICIENT_BUFFER = 122;
}
}
+37
View File
@@ -0,0 +1,37 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
/* NOTE: Parts of the code in this file are based on the work of Jan Axelson
* See http://www.lvr.com/winusb.htm for more information
*/
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
namespace MadWizard.WinUSBNet.API
{
/// <summary>
/// API declarations relating to file I/O (and used by WinUsb).
/// </summary>
sealed internal class FileIO
{
public const Int32 FILE_ATTRIBUTE_NORMAL = 0X80;
public const Int32 FILE_FLAG_OVERLAPPED = 0X40000000;
public const Int32 FILE_SHARE_READ = 1;
public const Int32 FILE_SHARE_WRITE = 2;
public const UInt32 GENERIC_READ = 0X80000000;
public const UInt32 GENERIC_WRITE = 0X40000000;
public static readonly IntPtr INVALID_HANDLE_VALUE = new(-1);
public const Int32 OPEN_EXISTING = 3;
public const Int32 ERROR_IO_PENDING = 997;
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle CreateFile(String lpFileName, UInt32 dwDesiredAccess, Int32 dwShareMode, IntPtr lpSecurityAttributes, Int32 dwCreationDisposition, Int32 dwFlagsAndAttributes, Int32 hTemplateFile);
}
}
+534
View File
@@ -0,0 +1,534 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
/* NOTE: Parts of the code in this file are based on the work of Jan Axelson
* See http://www.lvr.com/winusb.htm for more information
*/
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
namespace MadWizard.WinUSBNet.API
{
/// <summary>
/// Wrapper for a WinUSB device dealing with the WinUSB and additional interface handles
/// </summary>
internal partial class WinUSBDevice : IDisposable
{
private bool _disposed = false;
private SafeFileHandle _deviceHandle;
private IntPtr _winUsbHandle = IntPtr.Zero;
private IntPtr[] _addInterfaces = null;
public WinUSBDevice()
{
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~WinUSBDevice()
{
Dispose(false);
}
private void CheckNotDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException("USB device object has been disposed.");
}
}
// TODO: check if not disposed on methods (although this is already checked by USBDevice)
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// Dispose managed resources
if (_deviceHandle?.IsInvalid == false)
{
_deviceHandle.Dispose();
}
_deviceHandle = null;
}
// Dispose unmanaged resources
FreeWinUSB();
_disposed = true;
}
private void FreeWinUSB()
{
if (_addInterfaces != null)
{
for (int i = 0; i < _addInterfaces.Length; i++)
{
WinUsb_Free(_addInterfaces[i]);
}
_addInterfaces = null;
}
if (_winUsbHandle != IntPtr.Zero)
{
WinUsb_Free(_winUsbHandle);
}
_winUsbHandle = IntPtr.Zero;
}
public USB_DEVICE_DESCRIPTOR GetDeviceDescriptor()
{
uint size = (uint)Marshal.SizeOf(typeof(USB_DEVICE_DESCRIPTOR));
bool success = WinUsb_GetDescriptor(_winUsbHandle, USB_DEVICE_DESCRIPTOR_TYPE,
0, 0, out USB_DEVICE_DESCRIPTOR deviceDesc, size, out uint transfered);
if (!success)
{
throw APIException.Win32("Failed to get USB device descriptor.");
}
if (transfered != size)
{
throw APIException.Win32("Incomplete USB device descriptor.");
}
return deviceDesc;
}
private int ReadStringDescriptor(byte index, ushort languageID, byte[] buffer)
{
bool success = WinUsb_GetDescriptor(_winUsbHandle, USB_STRING_DESCRIPTOR_TYPE,
index, languageID, buffer, (uint)buffer.Length, out uint transfered);
if (!success)
{
throw APIException.Win32("Failed to get USB string descriptor (" + index + ").");
}
if (transfered == 0)
{
throw new APIException("No data returned when reading USB descriptor.");
}
int length = buffer[0];
if (length != transfered)
{
throw new APIException("Unexpected length when reading USB descriptor.");
}
return length;
}
public ushort[] GetSupportedLanguageIDs()
{
byte[] buffer = new byte[256];
int length = ReadStringDescriptor(0, 0, buffer);
length -= 2; // Skip length byte and descriptor type
if (length < 0 || (length % 2) != 0)
{
throw new APIException("Unexpected length when reading supported languages.");
}
ushort[] langIDs = new ushort[length / 2];
Buffer.BlockCopy(buffer, 2, langIDs, 0, length);
return langIDs;
}
public string GetStringDescriptor(byte index, ushort languageID)
{
byte[] buffer = new byte[256];
int length = ReadStringDescriptor(index, languageID, buffer);
length -= 2; // Skip length byte and descriptor type
if (length < 0)
{
return null;
}
char[] chars = System.Text.Encoding.Unicode.GetChars(buffer, 2, length);
return new string(chars);
}
public int ControlTransfer(byte requestType, byte request, ushort value, ushort index, ushort length, byte[] data)
{
uint bytesReturned = 0;
WINUSB_SETUP_PACKET setupPacket;
setupPacket.RequestType = requestType;
setupPacket.Request = request;
setupPacket.Value = value;
setupPacket.Index = index;
setupPacket.Length = length;
bool success = WinUsb_ControlTransfer(_winUsbHandle, setupPacket, data, length, ref bytesReturned, IntPtr.Zero);
if (!success) // todo check bytes returned?
{
throw APIException.Win32("Control transfer on WinUSB device failed.");
}
return (int)bytesReturned;
}
public void OpenDevice(string devicePathName)
{
try
{
_deviceHandle = FileIO.CreateFile(devicePathName,
FileIO.GENERIC_WRITE | FileIO.GENERIC_READ,
FileIO.FILE_SHARE_READ | FileIO.FILE_SHARE_WRITE,
IntPtr.Zero,
FileIO.OPEN_EXISTING,
FileIO.FILE_ATTRIBUTE_NORMAL | FileIO.FILE_FLAG_OVERLAPPED,
0);
if (_deviceHandle.IsInvalid)
{
throw APIException.Win32("Failed to open WinUSB device handle.");
}
InitializeDevice();
}
catch (Exception)
{
if (_deviceHandle != null)
{
_deviceHandle.Dispose();
_deviceHandle = null;
}
FreeWinUSB();
throw;
}
}
private IntPtr InterfaceHandle(int index)
{
if (index == 0)
{
return _winUsbHandle;
}
return _addInterfaces[index - 1];
}
public int InterfaceCount
{
get
{
return 1 + ((_addInterfaces?.Length) ?? 0);
}
}
public void GetInterfaceInfo(int interfaceIndex, out USB_INTERFACE_DESCRIPTOR descriptor, out WINUSB_PIPE_INFORMATION[] pipes)
{
var pipeList = new List<WINUSB_PIPE_INFORMATION>();
bool success = WinUsb_QueryInterfaceSettings(InterfaceHandle(interfaceIndex), 0, out descriptor);
if (!success)
{
throw APIException.Win32("Failed to get WinUSB device interface descriptor.");
}
IntPtr interfaceHandle = InterfaceHandle(interfaceIndex);
for (byte pipeIdx = 0; pipeIdx < descriptor.bNumEndpoints; pipeIdx++)
{
success = WinUsb_QueryPipe(interfaceHandle, 0, pipeIdx, out WINUSB_PIPE_INFORMATION pipeInfo);
pipeList.Add(pipeInfo);
if (!success)
{
throw APIException.Win32("Failed to get WinUSB device pipe information.");
}
}
pipes = [.. pipeList];
}
private void InitializeDevice()
{
bool success = WinUsb_Initialize(_deviceHandle, ref _winUsbHandle);
if (!success)
{
throw APIException.Win32("Failed to initialize WinUSB handle. Device might not be connected.");
}
List<IntPtr> interfaces = [];
byte numAddInterfaces = 0;
byte idx = 0;
try
{
while (true)
{
IntPtr ifaceHandle = IntPtr.Zero;
success = WinUsb_GetAssociatedInterface(_winUsbHandle, idx, out ifaceHandle);
if (!success)
{
if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
{
break;
}
throw APIException.Win32("Failed to enumerate interfaces for WinUSB device.");
}
interfaces.Add(ifaceHandle);
idx++;
numAddInterfaces++;
}
}
finally
{
// Save interface handles (will be cleaned by Dispose)
// also in case of exception (which is why it is in finally block),
// because some handles might have already been opened and need
// to be disposed.
_addInterfaces = [.. interfaces];
}
// Bind handle (needed for overlapped I/O thread pool)
ThreadPool.BindHandle(_deviceHandle);
// TODO: bind interface handles as well? doesn't seem to be necessary
}
public void ReadPipe(int ifaceIndex, byte pipeID, byte[] buffer, int offset, int bytesToRead, out uint bytesRead)
{
bool success;
unsafe
{
fixed (byte* pBuffer = buffer)
{
//WPinternals.LogFile.Log("Start WinUSB_ReadPipe");
success = WinUsb_ReadPipe(InterfaceHandle(ifaceIndex), pipeID, pBuffer + offset, (uint)bytesToRead,
out bytesRead, IntPtr.Zero);
//WPinternals.LogFile.Log("End WinUSB_ReadPipe: " + (success ? "True" : "False"));
}
}
if (!success)
{
throw APIException.Win32("Failed to read pipe on WinUSB device.");
}
}
private unsafe void HandleOverlappedAPI(bool success, string errorMessage, NativeOverlapped* pOverlapped, USBAsyncResult result, int bytesTransfered)
{
if (!success)
{
if (Marshal.GetLastWin32Error() != FileIO.ERROR_IO_PENDING)
{
Overlapped.Unpack(pOverlapped);
Overlapped.Free(pOverlapped);
throw APIException.Win32(errorMessage);
}
}
else
{
// Immediate success!
Overlapped.Unpack(pOverlapped);
Overlapped.Free(pOverlapped);
result.OnCompletion(true, null, bytesTransfered, false);
// is the callback still called in this case?? todo
}
}
public void ReadPipeOverlapped(int ifaceIndex, byte pipeID, byte[] buffer, int offset, int bytesToRead, USBAsyncResult result)
{
Overlapped overlapped = new()
{
AsyncResult = result
};
unsafe
{
NativeOverlapped* pOverlapped = null;
uint bytesRead;
pOverlapped = overlapped.Pack(PipeIOCallback, buffer);
bool success;
// Buffer is pinned already by overlapped.Pack
fixed (byte* pBuffer = buffer)
{
success = WinUsb_ReadPipe(InterfaceHandle(ifaceIndex), pipeID, pBuffer + offset, (uint)bytesToRead,
out bytesRead, pOverlapped);
}
HandleOverlappedAPI(success, "Failed to asynchronously read pipe on WinUSB device.", pOverlapped, result, (int)bytesRead);
}
}
public void WriteOverlapped(int ifaceIndex, byte pipeID, byte[] buffer, int offset, int bytesToWrite, USBAsyncResult result)
{
Overlapped overlapped = new()
{
AsyncResult = result
};
unsafe
{
NativeOverlapped* pOverlapped = null;
uint bytesWritten;
pOverlapped = overlapped.Pack(PipeIOCallback, buffer);
bool success;
// Buffer is pinned already by overlapped.Pack
fixed (byte* pBuffer = buffer)
{
success = WinUsb_WritePipe(InterfaceHandle(ifaceIndex), pipeID, pBuffer + offset, (uint)bytesToWrite,
out bytesWritten, pOverlapped);
}
HandleOverlappedAPI(success, "Failed to asynchronously write pipe on WinUSB device.", pOverlapped, result, (int)bytesWritten);
}
}
public void ControlTransferOverlapped(byte requestType, byte request, ushort value, ushort index, ushort length, byte[] data, USBAsyncResult result)
{
uint bytesReturned = 0;
WINUSB_SETUP_PACKET setupPacket;
setupPacket.RequestType = requestType;
setupPacket.Request = request;
setupPacket.Value = value;
setupPacket.Index = index;
setupPacket.Length = length;
Overlapped overlapped = new()
{
AsyncResult = result
};
unsafe
{
NativeOverlapped* pOverlapped = null;
pOverlapped = overlapped.Pack(PipeIOCallback, data);
bool success = WinUsb_ControlTransfer(_winUsbHandle, setupPacket, data, length, ref bytesReturned, pOverlapped);
HandleOverlappedAPI(success, "Asynchronous control transfer on WinUSB device failed.", pOverlapped, result, (int)bytesReturned);
}
}
private unsafe void PipeIOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped)
{
try
{
Exception error = null;
if (errorCode != 0)
{
error = APIException.Win32("Asynchronous operation on WinUSB device failed.", (int)errorCode);
}
Overlapped overlapped = Overlapped.Unpack(pOverlapped);
USBAsyncResult result = (USBAsyncResult)overlapped.AsyncResult;
Overlapped.Free(pOverlapped);
pOverlapped = null;
result.OnCompletion(false, error, (int)numBytes, true);
}
finally
{
if (pOverlapped != null)
{
Overlapped.Unpack(pOverlapped);
Overlapped.Free(pOverlapped);
}
}
}
public void AbortPipe(int ifaceIndex, byte pipeID)
{
bool success = WinUsb_AbortPipe(InterfaceHandle(ifaceIndex), pipeID);
if (!success)
{
throw APIException.Win32("Failed to abort pipe on WinUSB device.");
}
}
public void ResetPipe(int ifaceIndex, byte pipeID)
{
bool success = WinUsb_ResetPipe(InterfaceHandle(ifaceIndex), pipeID);
if (!success)
{
throw APIException.Win32("Failed to reset pipe on WinUSB device.");
}
}
public void WritePipe(int ifaceIndex, byte pipeID, byte[] buffer, int offset, int length)
{
uint bytesWritten;
bool success;
unsafe
{
fixed (byte* pBuffer = buffer)
{
//WPinternals.LogFile.Log("Start WinUSB_WritePipe");
success = WinUsb_WritePipe(InterfaceHandle(ifaceIndex), pipeID, pBuffer + offset, (uint)length,
out bytesWritten, IntPtr.Zero);
//WPinternals.LogFile.Log("End WinUSB_WritePipe: " + (success ? "True" : "False"));
}
}
if (!success || (bytesWritten != length))
{
throw APIException.Win32("Failed to write pipe on WinUSB device.");
}
}
public void FlushPipe(int ifaceIndex, byte pipeID)
{
bool success = WinUsb_FlushPipe(InterfaceHandle(ifaceIndex), pipeID);
if (!success)
{
throw APIException.Win32("Failed to flush pipe on WinUSB device.");
}
}
public void SetPipePolicy(int ifaceIndex, byte pipeID, POLICY_TYPE policyType, bool value)
{
byte byteVal = (byte)(value ? 1 : 0);
bool success = WinUsb_SetPipePolicy(InterfaceHandle(ifaceIndex), pipeID, (uint)policyType, 1, ref byteVal);
if (!success)
{
throw APIException.Win32("Failed to set WinUSB pipe policy.");
}
}
public void SetPipePolicy(int ifaceIndex, byte pipeID, POLICY_TYPE policyType, uint value)
{
bool success = WinUsb_SetPipePolicy(InterfaceHandle(ifaceIndex), pipeID, (uint)policyType, 4, ref value);
if (!success)
{
throw APIException.Win32("Failed to set WinUSB pipe policy.");
}
}
public bool GetPipePolicyBool(int ifaceIndex, byte pipeID, POLICY_TYPE policyType)
{
uint length = 1;
bool success = WinUsb_GetPipePolicy(InterfaceHandle(ifaceIndex), pipeID, (uint)policyType, ref length, out byte result);
if (!success || length != 1)
{
throw APIException.Win32("Failed to get WinUSB pipe policy.");
}
return result != 0;
}
public uint GetPipePolicyUInt(int ifaceIndex, byte pipeID, POLICY_TYPE policyType)
{
uint length = 4;
bool success = WinUsb_GetPipePolicy(InterfaceHandle(ifaceIndex), pipeID, (uint)policyType, ref length, out uint result);
if (!success || length != 4)
{
throw APIException.Win32("Failed to get WinUSB pipe policy.");
}
return result;
}
}
}
+199
View File
@@ -0,0 +1,199 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
/* NOTE: Parts of the code in this file are based on the work of Jan Axelson
* See http://www.lvr.com/winusb.htm for more information
*/
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace MadWizard.WinUSBNet.API
{
[StructLayout(LayoutKind.Sequential)]
internal struct USB_DEVICE_DESCRIPTOR
{
public byte bLength;
public byte bDescriptorType;
public ushort bcdUSB;
public byte bDeviceClass;
public byte bDeviceSubClass;
public byte bDeviceProtocol;
public byte bMaxPacketSize0;
public ushort idVendor;
public ushort idProduct;
public ushort bcdDevice;
public byte iManufacturer;
public byte iProduct;
public byte iSerialNumber;
public byte bNumConfigurations;
};
[StructLayout(LayoutKind.Sequential)]
internal struct USB_CONFIGURATION_DESCRIPTOR
{
public byte bLength;
public byte bDescriptorType;
public ushort wTotalLength;
public byte bNumInterfaces;
public byte bConfigurationValue;
public byte iConfiguration;
public byte bmAttributes;
public byte MaxPower;
}
[StructLayout(LayoutKind.Sequential)]
internal struct USB_INTERFACE_DESCRIPTOR
{
public byte bLength;
public byte bDescriptorType;
public byte bInterfaceNumber;
public byte bAlternateSetting;
public byte bNumEndpoints;
public byte bInterfaceClass;
public byte bInterfaceSubClass;
public byte bInterfaceProtocol;
public byte iInterface;
};
internal enum USBD_PIPE_TYPE : int
{
UsbdPipeTypeControl,
UsbdPipeTypeIsochronous,
UsbdPipeTypeBulk,
UsbdPipeTypeInterrupt,
}
[StructLayout(LayoutKind.Sequential)]
internal struct WINUSB_PIPE_INFORMATION
{
public USBD_PIPE_TYPE PipeType;
public byte PipeId;
public ushort MaximumPacketSize;
public byte Interval;
}
internal enum POLICY_TYPE : int
{
SHORT_PACKET_TERMINATE = 1,
AUTO_CLEAR_STALL,
PIPE_TRANSFER_TIMEOUT,
IGNORE_SHORT_PACKETS,
ALLOW_PARTIAL_READS,
AUTO_FLUSH,
RAW_IO,
}
internal partial class WinUSBDevice
{
private const UInt32 DEVICE_SPEED = 1;
private enum USB_DEVICE_SPEED : int
{
UsbLowSpeed = 1,
UsbFullSpeed,
UsbHighSpeed,
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct WINUSB_SETUP_PACKET
{
public byte RequestType;
public byte Request;
public ushort Value;
public ushort Index;
public ushort Length;
}
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_ControlTransfer(IntPtr InterfaceHandle, WINUSB_SETUP_PACKET SetupPacket, Byte[] Buffer, UInt32 BufferLength, ref UInt32 LengthTransferred, IntPtr Overlapped);
[DllImport("winusb.dll", SetLastError = true)]
private static unsafe extern bool WinUsb_ControlTransfer(IntPtr InterfaceHandle, WINUSB_SETUP_PACKET SetupPacket, Byte[] Buffer, UInt32 BufferLength, ref UInt32 LengthTransferred, NativeOverlapped* pOverlapped);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_Free(IntPtr InterfaceHandle);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_Initialize(SafeFileHandle DeviceHandle, ref IntPtr InterfaceHandle);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_QueryDeviceInformation(IntPtr InterfaceHandle, UInt32 InformationType, ref UInt32 BufferLength, out byte Buffer);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_QueryInterfaceSettings(IntPtr InterfaceHandle, Byte AlternateInterfaceNumber, out USB_INTERFACE_DESCRIPTOR UsbAltInterfaceDescriptor);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_QueryPipe(IntPtr InterfaceHandle, Byte AlternateInterfaceNumber, Byte PipeIndex, out WINUSB_PIPE_INFORMATION PipeInformation);
[DllImport("winusb.dll", SetLastError = true)]
private static unsafe extern bool WinUsb_ReadPipe(IntPtr InterfaceHandle, byte PipeID, byte* pBuffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);
[DllImport("winusb.dll", SetLastError = true)]
private static unsafe extern bool WinUsb_ReadPipe(IntPtr InterfaceHandle, byte PipeID, byte* pBuffer, uint BufferLength, out uint LengthTransferred, NativeOverlapped* pOverlapped);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_AbortPipe(IntPtr InterfaceHandle, byte PipeID);
// Two declarations for WinUsb_SetPipePolicy.
// Use this one when the returned Value is a Byte (all except PIPE_TRANSFER_TIMEOUT):
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_SetPipePolicy(IntPtr InterfaceHandle, Byte PipeID, UInt32 PolicyType, UInt32 ValueLength, ref byte Value);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_GetPipePolicy(IntPtr InterfaceHandle, Byte PipeID, UInt32 PolicyType, ref UInt32 ValueLength, out byte Value);
// Use this alias when the returned Value is a UInt32 (PIPE_TRANSFER_TIMEOUT only):
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_SetPipePolicy(IntPtr InterfaceHandle, Byte PipeID, UInt32 PolicyType, UInt32 ValueLength, ref UInt32 Value);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_GetPipePolicy(IntPtr InterfaceHandle, Byte PipeID, UInt32 PolicyType, ref UInt32 ValueLength, out UInt32 Value);
[DllImport("winusb.dll", SetLastError = true)]
private static unsafe extern bool WinUsb_WritePipe(IntPtr InterfaceHandle, byte PipeID, byte* pBuffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);
[DllImport("winusb.dll", SetLastError = true)]
private static unsafe extern bool WinUsb_WritePipe(IntPtr InterfaceHandle, byte PipeID, byte* pBuffer, uint BufferLength, out uint LengthTransferred, NativeOverlapped* pOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
private static unsafe extern bool CancelIo(IntPtr hFile);
[DllImport("kernel32.dll", SetLastError = true)]
private static unsafe extern bool CancelIoEx(IntPtr hFile, NativeOverlapped* pOverlapped);
[DllImport("winusb.dll", SetLastError = true)]
private static unsafe extern bool WinUsb_ResetPipe(IntPtr InterfaceHandle, byte PipeID);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_FlushPipe(IntPtr InterfaceHandle, byte PipeID);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_GetDescriptor(IntPtr InterfaceHandle, byte DescriptorType,
byte Index, UInt16 LanguageID, byte[] Buffer, UInt32 BufferLength, out UInt32 LengthTransfered);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_GetDescriptor(IntPtr InterfaceHandle, byte DescriptorType,
byte Index, UInt16 LanguageID, out USB_DEVICE_DESCRIPTOR deviceDesc, UInt32 BufferLength, out UInt32 LengthTransfered);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_GetDescriptor(IntPtr InterfaceHandle, byte DescriptorType,
byte Index, UInt16 LanguageID, out USB_CONFIGURATION_DESCRIPTOR deviceDesc, UInt32 BufferLength, out UInt32 LengthTransfered);
[DllImport("winusb.dll", SetLastError = true)]
private static extern bool WinUsb_GetAssociatedInterface(IntPtr InterfaceHandle, byte AssociatedInterfaceIndex,
out IntPtr AssociatedInterfaceHandle);
private const int USB_DEVICE_DESCRIPTOR_TYPE = 0x01;
private const int USB_CONFIGURATION_DESCRIPTOR_TYPE = 0x02;
private const int USB_STRING_DESCRIPTOR_TYPE = 0x03;
private const int ERROR_NO_MORE_ITEMS = 259;
}
}
+194
View File
@@ -0,0 +1,194 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
using System.Windows;
using System.Windows.Interop;
namespace MadWizard.WinUSBNet
{
internal class DeviceNotifyHook : IDisposable
{
// http://msdn.microsoft.com/en-us/library/system.windows.forms.nativewindow.aspx
// TODO: disposed exception when disposed
private readonly USBNotifier _notifier;
private Guid _guid;
private readonly IntPtr _notifyHandle;
public DeviceNotifyHook(USBNotifier notifier, Guid guid)
{
// _parent = parent;
_guid = guid;
// _parent.HandleCreated += new EventHandler(this.OnHandleCreated);
// _parent.HandleDestroyed += new EventHandler(this.OnHandleDestroyed);
_notifier = notifier;
IntPtr hWnd = IntPtr.Zero;
if (System.Windows.Application.Current.MainWindow != null)
{
hWnd = new WindowInteropHelper(System.Windows.Application.Current.MainWindow).Handle;
}
if (hWnd == IntPtr.Zero)
{
throw new USBException("Main window not loaded yet. Cannot connect with USB yet.");
}
API.DeviceManagement.RegisterForDeviceNotifications(hWnd, _guid, ref _notifyHandle);
HwndSource source = PresentationSource.FromVisual(System.Windows.Application.Current.MainWindow) as HwndSource;
source.AddHook(WndProc);
}
~DeviceNotifyHook()
{
Dispose(false);
}
/*
// Listen for the control's window creation and then hook into it.
private void OnHandleCreated(object sender, EventArgs e)
{
try
{
// Window is now created, assign handle to NativeWindow.
IntPtr handle = ((Control)sender).Handle;
RegisterNotify(handle);
}
catch (API.APIException ex)
{
throw new USBException("Failed to register new window handle for device notification.", ex);
}
}
private void OnHandleDestroyed(object sender, EventArgs e)
{
try
{
// Window was destroyed, release hook.
StopNotify();
}
catch (API.APIException ex)
{
throw new USBException("Failed to unregister destroyed window handle for device notification.", ex);
}
}
private void RegisterNotify(IntPtr handle)
{
AssignHandle(handle);
if (_notifyHandle != IntPtr.Zero)
{
API.DeviceManagement.StopDeviceDeviceNotifications(_notifyHandle);
_notifyHandle = IntPtr.Zero;
}
API.DeviceManagement.RegisterForDeviceNotifications(handle, _guid, ref _notifyHandle);
}
private void StopNotify()
{
//ReleaseHandle();
if (_notifyHandle != IntPtr.Zero)
{
API.DeviceManagement.StopDeviceDeviceNotifications(_notifyHandle);
_notifyHandle = IntPtr.Zero;
}
}
protected override void WndProc(ref Message m)
{
// Listen for operating system messages
switch (m.Msg)
{
case API.DeviceManagement.WM_DEVICECHANGE:
_notifier.HandleDeviceChange(m);
break;
case WM_NCDESTROY:
// Note: when a control is used, OnHandleDestroyed will be called and the
// handle is already released from NativeWindow. In that case, this
// WM_NCDESTROY message will not be caught here. This is no problem since
// StopNotify is already called. Even if it does, calling it twice does not cause
// problems.
// When a window handle is used instead of a Control the OnHandle events will not
// fire and this handler is necessary to release the handle and stop notifications
// when the window is destroyed.
StopNotify();
break;
}
base.WndProc(ref m);
}
*/
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Listen for operating system messages
return msg switch
{
API.DeviceManagement.WM_DEVICECHANGE => (IntPtr)_notifier.HandleDeviceChange(msg, wParam, lParam),
_ => IntPtr.Zero,
};
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// clean managed resources
// do not clean the notifier here. the notifier owns and will dispose this object.
}
if (_notifyHandle != IntPtr.Zero)
{
API.DeviceManagement.StopDeviceDeviceNotifications(_notifyHandle);
try
{
/*
if ((Application.Current != null) && (Application.Current.MainWindow != null))
{
HwndSource source = PresentationSource.FromVisual(Application.Current.MainWindow) as HwndSource;
source.RemoveHook(WndProc);
}
*/
void RemoveHookAction()
{
if (System.Windows.Application.Current.MainWindow != null)
{
HwndSource source = PresentationSource.FromVisual(System.Windows.Application.Current.MainWindow) as HwndSource;
source.RemoveHook(WndProc);
}
}
if (System.Windows.Application.Current != null)
{
if (System.Windows.Application.Current.Dispatcher.Thread.ManagedThreadId == Environment.CurrentManagedThreadId)
{
RemoveHookAction();
}
else
{
System.Windows.Application.Current.Dispatcher.Invoke(RemoveHookAction);
}
}
}
catch { }
}
}
}
}
@@ -0,0 +1,111 @@
// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// Some of the classes and functions in this file were found online.
// Where possible the original authors are referenced.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace WPinternals.HelperClasses
{
internal class AsyncAutoResetEvent
{
private readonly LinkedList<TaskCompletionSource<bool>> waiters =
new();
private bool isSignaled;
public AsyncAutoResetEvent(bool signaled)
{
isSignaled = signaled;
}
public Task<bool> WaitAsync(TimeSpan timeout)
{
return WaitAsync(timeout, CancellationToken.None);
}
public async Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
TaskCompletionSource<bool> tcs;
lock (waiters)
{
if (isSignaled)
{
isSignaled = false;
return true;
}
else if (timeout == TimeSpan.Zero)
{
return isSignaled;
}
else
{
tcs = new TaskCompletionSource<bool>();
waiters.AddLast(tcs);
}
}
Task winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken));
if (winner == tcs.Task)
{
// The task was signaled.
return true;
}
else
{
// We timed-out; remove our reference to the task.
// This is an O(n) operation since waiters is a LinkedList<T>.
lock (waiters)
{
bool removed = waiters.Remove(tcs);
System.Diagnostics.Debug.Assert(removed);
return false;
}
}
}
public void Set()
{
TaskCompletionSource<bool> toRelease = null;
lock (waiters)
{
if (waiters.Count > 0)
{
// Signal the first task in the waiters list.
toRelease = waiters.First.Value;
waiters.RemoveFirst();
}
else if (!isSignaled)
{
// No tasks are pending
isSignaled = true;
}
}
toRelease?.SetResult(true);
}
}
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// Some of the classes and functions in this file were found online.
// Where possible the original authors are referenced.
using System;
using System.Text;
namespace WPinternals.HelperClasses
{
public static class Converter
{
public static string ConvertHexToString(byte[] Bytes, string Separator)
{
StringBuilder s = new(1000);
for (int i = Bytes.GetLowerBound(0); i <= Bytes.GetUpperBound(0); i++)
{
if (i != Bytes.GetLowerBound(0))
{
s.Append(Separator);
}
s.Append(Bytes[i].ToString("X2"));
}
return s.ToString();
}
public static byte[] ConvertStringToHex(string HexString)
{
if (HexString.Length % 2 == 1)
{
throw new Exception("The binary key cannot have an odd number of digits");
}
byte[] arr = new byte[HexString.Length >> 1];
for (int i = 0; i < HexString.Length >> 1; ++i)
{
arr[i] = (byte)((GetHexVal(HexString[i << 1]) << 4) + GetHexVal(HexString[(i << 1) + 1]));
}
return arr;
}
public static int GetHexVal(char hex)
{
int val = hex;
//For uppercase A-F letters:
//return val - (val < 58 ? 48 : 55);
//For lowercase a-f letters:
//return val - (val < 58 ? 48 : 87);
//Or the two combined, but a bit slower:
return val - (val < 58 ? 48 : val < 97 ? 55 : 87);
}
}
}
+76
View File
@@ -0,0 +1,76 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
namespace MadWizard.WinUSBNet
{
/// <summary>
/// USB base class code enumeration, as defined in the USB specification
/// </summary>
public enum USBBaseClass
{
/// <summary>Unknown non-zero class code. Used when the actual class code
/// does not match any of the ones defined in this enumeration. </summary>
Unknown = -1,
/// <summary>Base class defined elsewhere (0x00)</summary>
None = 0x00,
/// <summary>Audio base class (0x01)</summary>
Audio = 0x01,
/// <summary>Communications and CDC control base class (0x02)</summary>
CommCDC = 0x02,
/// <summary>HID base class (0x03)</summary>
HID = 0x03,
/// <summary>Physical base class (0x05)</summary>
Physical = 0x05,
/// <summary>Image base class (0x06)</summary>
Image = 0x06,
/// <summary>Printer base class (0x07)</summary>
Printer = 0x07,
/// <summary>Mass storage base class (0x08)</summary>
MassStorage = 0x08,
/// <summary>Hub base class (0x09)</summary>
Hub = 0x09,
/// <summary>CDC data base class (0x0A)</summary>
CDCData = 0x0A,
/// <summary>Smart card base class (0x0B)</summary>
SmartCard = 0x0B,
/// <summary>Content security base class (0x0D)</summary>
ContentSecurity = 0x0D,
/// <summary>Video base class (0x0E)</summary>
Video = 0x0E,
/// <summary>Personal health care base class (0x0F)</summary>
PersonalHealthcare = 0x0F,
/// <summary>Diagnostic device base class (0xDC)</summary>
DiagnosticDevice = 0xDC,
/// <summary>Wireless controller base class (0xE0)</summary>
WirelessController = 0xE0,
/// <summary>Miscellaneous base class (0xEF)</summary>
Miscellaneous = 0xEF,
/// <summary>Application specific base class (0xFE)</summary>
ApplicationSpecific = 0xFE,
/// <summary>Vendor specific base class (0xFF)</summary>
VendorSpecific = 0xFF,
};
}
+127
View File
@@ -0,0 +1,127 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
using System.Threading;
namespace MadWizard.WinUSBNet
{
internal class USBAsyncResult : IAsyncResult, IDisposable
{
private readonly AsyncCallback _userCallback;
private bool _completed;
private bool _completedSynchronously;
private ManualResetEvent _waitEvent;
private int _bytesTransfered;
private Exception _error;
public USBAsyncResult(AsyncCallback userCallback, object stateObject)
{
AsyncState = stateObject;
_userCallback = userCallback;
_completedSynchronously = false;
_completed = false;
_waitEvent = null;
}
public object AsyncState { get; }
public Exception Error
{
get
{
lock (this)
{
return _error;
}
}
}
public int BytesTransfered
{
get { return _bytesTransfered; }
}
public WaitHandle AsyncWaitHandle
{
get
{
lock (this)
{
if (_waitEvent == null)
{
_waitEvent = new ManualResetEvent(_completed);
}
}
return _waitEvent;
}
}
public bool CompletedSynchronously
{
get
{
lock (this)
{
return _completedSynchronously;
}
}
}
public bool IsCompleted
{
get
{
lock (this)
{
return _completed;
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void OnCompletion(bool completedSynchronously, Exception error, int bytesTransfered, bool synchronousCallback)
{
lock (this)
{
_completedSynchronously = completedSynchronously;
_completed = true;
_error = error;
_bytesTransfered = bytesTransfered;
_waitEvent?.Set();
}
if (_userCallback != null)
{
if (synchronousCallback)
{
RunCallback(null);
}
else
{
ThreadPool.QueueUserWorkItem(RunCallback);
}
}
}
private void RunCallback(object state)
{
_userCallback(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Cleanup managed resources
lock (this)
{
_waitEvent?.Close();
}
}
}
}
}
+871
View File
@@ -0,0 +1,871 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
using System.Collections.Generic;
namespace MadWizard.WinUSBNet
{
/// <summary>
/// The UsbDevice class represents a single WinUSB device.
/// </summary>
public class USBDevice : IDisposable
{
private bool _disposed = false;
/// <summary>
/// Collection of all pipes available on the USB device
/// </summary>
public USBPipeCollection Pipes
{
get;
private set;
}
/// <summary>
/// The input pipe. Use this pipe to read from the device.
/// </summary>
public USBPipe InputPipe
{
get;
private set;
}
/// <summary>
/// The output pipe. Use this pipe to write to the device.
/// </summary>
public USBPipe OutputPipe
{
get;
private set;
}
/// <summary>
/// Collection of all interfaces available on the USB device
/// </summary>
public USBInterfaceCollection Interfaces
{
get;
private set;
}
/// <summary>
/// Device descriptor with information about the device
/// </summary>
public USBDeviceDescriptor Descriptor
{
get;
}
/// <summary>
/// Constructs a new USB device
/// </summary>
/// <param name="deviceInfo">USB device info of the device to create</param>
public USBDevice(USBDeviceInfo deviceInfo)
: this(deviceInfo.DevicePath)
{
// Handled in other constructor
}
/// <summary>
/// Disposes the UsbDevice including all unmanaged WinUSB handles. This function
/// should be called when the UsbDevice object is no longer in use, otherwise
/// unmanaged handles will remain open until the garbage collector finalizes the
/// object.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Finalizer for the UsbDevice. Disposes all unmanaged handles.
/// </summary>
~USBDevice()
{
Dispose(false);
}
/// <summary>
/// Disposes the object
/// </summary>
/// <param name="disposing">Indicates whether Dispose was called manually (true) or by
/// the garbage collector (false) via the destructor.</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
InternalDevice?.Dispose();
}
// Clean unmanaged resources here.
// (none currently)
_disposed = true;
}
/// <summary>
/// Constructs a new USB device
/// </summary>
/// <param name="devicePathName">Device path name of the USB device to create</param>
public USBDevice(string devicePathName)
{
try
{
Descriptor = GetDeviceDescriptor(devicePathName);
}
catch // (Exception Ex)
{
//WPinternals.LogFile.LogException(Ex);
}
InternalDevice = new API.WinUSBDevice();
try
{
InternalDevice.OpenDevice(devicePathName);
InitializeInterfaces();
}
catch (API.APIException e)
{
InternalDevice.Dispose();
throw new USBException("Failed to open device.", e);
}
}
internal API.WinUSBDevice InternalDevice { get; } = null;
private void InitializeInterfaces()
{
int numInterfaces = InternalDevice.InterfaceCount;
List<USBPipe> allPipes = [];
InputPipe = null;
OutputPipe = null;
USBInterface[] interfaces = new USBInterface[numInterfaces];
// UsbEndpoint
for (int i = 0; i < numInterfaces; i++)
{
InternalDevice.GetInterfaceInfo(i, out API.USB_INTERFACE_DESCRIPTOR descriptor, out API.WINUSB_PIPE_INFORMATION[] pipesInfo);
USBPipe[] interfacePipes = new USBPipe[pipesInfo.Length];
for(int k=0;k<pipesInfo.Length;k++)
{
USBPipe pipe = new(this, pipesInfo[k]);
interfacePipes[k] = pipe;
allPipes.Add(pipe);
if (Convert.ToBoolean(pipesInfo[k].PipeId & 0x80) && (InputPipe == null))
{
InputPipe = pipe;
}
if (!Convert.ToBoolean(pipesInfo[k].PipeId & 0x80) && (OutputPipe == null))
{
OutputPipe = pipe;
}
}
// TODO:
//if (descriptor.iInterface != 0)
// _wuDevice.GetStringDescriptor(descriptor.iInterface);
USBPipeCollection pipeCollection = new(interfacePipes);
interfaces[i] = new USBInterface(this, i, descriptor, pipeCollection);
}
Pipes = new USBPipeCollection([.. allPipes]);
Interfaces = new USBInterfaceCollection(interfaces);
}
private void CheckControlParams(int value, int index, byte[] buffer, int length)
{
if (value < ushort.MinValue || value > ushort.MaxValue)
{
throw new ArgumentOutOfRangeException(nameof(value), "Value parameter out of range.");
}
if (index < ushort.MinValue || index > ushort.MaxValue)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index parameter out of range.");
}
if (length > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(length), "Length parameter is larger than the size of the buffer.");
}
if (length > ushort.MaxValue)
{
throw new ArgumentOutOfRangeException(nameof(length), "Length too large");
}
}
/// <summary>
/// Specifies the timeout in milliseconds for control pipe operations. If a control transfer does not finish within the specified time it will fail.
/// When set to zero, no timeout is used. Default value is 5000 milliseconds.
/// </summary>
/// <seealso href="http://msdn.microsoft.com/en-us/library/aa476439.aspx">WinUSB_GetPipePolicy for a more detailed description</seealso>
public int ControlPipeTimeout
{
get
{
byte PipeID = 0;
if (InputPipe != null)
{
PipeID = InputPipe.Address;
}
return (int)InternalDevice.GetPipePolicyUInt(0, 0x00, API.POLICY_TYPE.PIPE_TRANSFER_TIMEOUT);
}
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "Control pipe timeout cannot be negative.");
}
//_wuDevice.SetPipePolicy(0, 0x00, API.POLICY_TYPE.PIPE_TRANSFER_TIMEOUT, (uint)value);
if (InputPipe != null)
{
InternalDevice.SetPipePolicy(0, InputPipe.Address, API.POLICY_TYPE.PIPE_TRANSFER_TIMEOUT, (uint)value);
}
if (OutputPipe != null)
{
InternalDevice.SetPipePolicy(0, OutputPipe.Address, API.POLICY_TYPE.PIPE_TRANSFER_TIMEOUT, (uint)value);
}
}
}
/// <summary>
/// Initiates a control transfer over the default control endpoint. This method allows both IN and OUT direction transfers, depending
/// on the highest bit of the <paramref name="requestType"/> parameter. Alternatively, <see cref="ControlIn(byte,byte,int,int,byte[],int)"/> and
/// <see cref="ControlOut(byte,byte,int,int,byte[],int)"/> can be used for control transfers in a specific direction, which is the recommended way because
/// it prevents using the wrong direction accidentally. Use the ControlTransfer method when the direction is not known at compile time.
/// </summary>
/// <param name="requestType">The setup packet request type.</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">The data to transfer in the data stage of the control. When the transfer is in the IN direction the data received will be
/// written to this buffer. For an OUT direction transfer the contents of the buffer are written sent through the pipe.</param>
/// <param name="length">Length of the data to transfer. Must be equal to or less than the length of <paramref name="buffer"/>.
/// The setup packet's length member will be set to this length.</param>
/// <returns>The number of bytes received from the device.</returns>
public int ControlTransfer(byte requestType, byte request, int value, int index, byte[] buffer, int length)
{
// Parameters are int and not ushort because ushort is not CLS compliant.
CheckNotDisposed();
CheckControlParams(value, index, buffer, length);
try
{
return InternalDevice.ControlTransfer(requestType, request, (ushort)value, (ushort)index, (ushort)length, buffer);
}
catch (API.APIException e)
{
throw new USBException("Control transfer failed", e);
}
}
/// <summary>
/// Initiates an asynchronous control transfer over the default control endpoint. This method allows both IN and OUT direction transfers, depending
/// on the highest bit of the <paramref name="requestType"/> parameter. Alternatively, <see cref="BeginControlIn(byte,byte,int,int,byte[],int,AsyncCallback,object)"/> and
/// <see cref="BeginControlIn(byte,byte,int,int,byte[],int,AsyncCallback,object)"/> can be used for asynchronous control transfers in a specific direction, which is
/// the recommended way because it prevents using the wrong direction accidentally. Use the BeginControlTransfer method when the direction is not
/// known at compile time. </summary>
/// <param name="requestType">The setup packet request type.</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">The data to transfer in the data stage of the control. When the transfer is in the IN direction the data received will be
/// written to this buffer. For an OUT direction transfer the contents of the buffer are written sent through the pipe. Note: This buffer is not allowed
/// to change for the duration of the asynchronous operation.</param>
/// <param name="length">Length of the data to transfer. Must be equal to or less than the length of <paramref name="buffer"/>. The setup packet's length member will be set to this length.</param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the control transfer is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous control transfer, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndControlTransfer"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndControlTransfer"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginControlTransfer(byte requestType, byte request, int value, int index, byte[] buffer, int length, AsyncCallback userCallback, object stateObject)
{
// Parameters are int and not ushort because ushort is not CLS compliant.
CheckNotDisposed();
CheckControlParams(value, index, buffer, length);
USBAsyncResult result = new(userCallback, stateObject);
try
{
InternalDevice.ControlTransferOverlapped(requestType, request, (ushort)value, (ushort)index, (ushort)length, buffer, result);
}
catch (API.APIException e)
{
result?.Dispose();
throw new USBException("Asynchronous control transfer failed", e);
}
catch (Exception)
{
result?.Dispose();
throw;
}
return result;
}
/// <summary>
/// Initiates an asynchronous control transfer over the default control endpoint. This method allows both IN and OUT direction transfers, depending
/// on the highest bit of the <paramref name="requestType"/> parameter. Alternatively, <see cref="BeginControlIn(byte,byte,int,int,byte[],int,AsyncCallback,object)"/> and
/// <see cref="BeginControlIn(byte,byte,int,int,byte[],int,AsyncCallback,object)"/> can be used for asynchronous control transfers in a specific direction, which is
/// the recommended way because it prevents using the wrong direction accidentally. Use the BeginControlTransfer method when the direction is not
/// known at compile time. </summary>
/// <param name="requestType">The setup packet request type.</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">The data to transfer in the data stage of the control. When the transfer is in the IN direction the data received will be
/// written to this buffer. For an OUT direction transfer the contents of the buffer are written sent through the pipe. The setup packet's length member will
/// be set to the length of this buffer. Note: This buffer is not allowed to change for the duration of the asynchronous operation. </param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the control transfer is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous control transfer, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndControlTransfer"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndControlTransfer"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginControlTransfer(byte requestType, byte request, int value, int index, byte[] buffer, AsyncCallback userCallback, object stateObject)
{
return BeginControlTransfer(requestType, request, value, index, buffer, buffer.Length, userCallback, stateObject);
}
/// <summary>
/// Waits for a pending asynchronous control transfer to complete.
/// </summary>
/// <param name="asyncResult">The <see cref="IAsyncResult"/> object representing the asynchronous operation,
/// as returned by one of the ControlIn, ControlOut or ControlTransfer methods.</param>
/// <returns>The number of bytes transfered during the operation.</returns>
/// <remarks>Every asynchronous control transfer must have a matching call to <see cref="EndControlTransfer"/> to dispose
/// of any resources used and to retrieve the result of the operation. When the operation was successful the method returns the number
/// of bytes that were transfered. If an error occurred during the operation this method will throw the exceptions that would
/// otherwise have occurred during the operation. If the operation is not yet finished EndControlTransfer will wait for the
/// operation to finish before returning.</remarks>
public int EndControlTransfer(IAsyncResult asyncResult)
{
if (asyncResult == null)
{
throw new NullReferenceException("asyncResult cannot be null");
}
if (!(asyncResult is USBAsyncResult))
{
throw new ArgumentException("AsyncResult object was not created by calling one of the BeginControl* methods on this class.");
}
// todo: check duplicate end control
USBAsyncResult result = (USBAsyncResult)asyncResult;
try
{
if (!result.IsCompleted)
{
result.AsyncWaitHandle.WaitOne();
}
if (result.Error != null)
{
throw new USBException("Asynchronous control transfer from pipe has failed.", result.Error);
}
return result.BytesTransfered;
}
finally
{
result.Dispose();
}
}
/// <summary>
/// Initiates a control transfer over the default control endpoint. This method allows both IN and OUT direction transfers, depending
/// on the highest bit of the <paramref name="requestType"/> parameter). Alternatively, <see cref="ControlIn(byte,byte,int,int,byte[])"/> and
/// <see cref="ControlOut(byte,byte,int,int,byte[])"/> can be used for control transfers in a specific direction, which is the recommended way because
/// it prevents using the wrong direction accidentally. Use the ControlTransfer method when the direction is not known at compile time.
/// </summary>
/// <param name="requestType">The setup packet request type.</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">The data to transfer in the data stage of the control. When the transfer is in the IN direction the data received will be
/// written to this buffer. For an OUT direction transfer the contents of the buffer are written sent through the pipe. The length of this
/// buffer is used as the number of bytes in the control transfer. The setup packet's length member will be set to this length as well.</param>
/// <returns>The number of bytes received from the device.</returns>
public int ControlTransfer(byte requestType, byte request, int value, int index, byte[] buffer)
{
return ControlTransfer(requestType, request, value, index, buffer, buffer.Length);
}
/// <summary>
/// Initiates a control transfer without a data stage over the default control endpoint. This method allows both IN and OUT direction transfers, depending
/// on the highest bit of the <paramref name="requestType"/> parameter). Alternatively, <see cref="ControlIn(byte,byte,int,int)"/> and
/// <see cref="ControlOut(byte,byte,int,int)"/> can be used for control transfers in a specific direction, which is the recommended way because
/// it prevents using the wrong direction accidentally. Use the ControlTransfer method when the direction is not known at compile time.
/// </summary>
/// <param name="requestType">The setup packet request type.</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
public void ControlTransfer(byte requestType, byte request, int value, int index)
{
// TODO: null instead of empty buffer. But overlapped code would have to be fixed for this (no buffer to pin)
ControlTransfer(requestType, request, value, index, [], 0);
}
private void CheckIn(byte requestType)
{
if ((requestType & 0x80) == 0) // Host to device?
{
throw new ArgumentException("Request type is not IN.");
}
}
private void CheckOut(byte requestType)
{
if ((requestType & 0x80) == 0x80) // Device to host?
{
throw new ArgumentException("Request type is not OUT.");
}
}
/// <summary>
/// Initiates a control transfer over the default control endpoint. The request should have an IN direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter). A buffer to receive the data is automatically created by this method.
/// </summary>
/// <param name="requestType">The setup packet request type. The request type must specify the IN direction (highest bit set).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="length">Length of the data to transfer. A buffer will be created with this length and the length member of the setup packet
/// will be set to this length.</param>
/// <returns>A buffer containing the data transfered.</returns>
/// <remarks>This routine initially allocates a buffer to hold the <paramref name="length"/> bytes of data expected from the device.
/// If the device responds with less data than expected, this routine will allocate a smaller buffer to copy and return only the bytes actually received.
/// </remarks>
public byte[] ControlIn(byte requestType, byte request, int value, int index, int length)
{
CheckIn(requestType);
byte[] buffer = new byte[length];
int actuallyReceived = ControlTransfer(requestType, request, value, index, buffer, buffer.Length);
if (actuallyReceived < length)
{
byte[] outBuffer = new byte[actuallyReceived];
Array.Copy(buffer, 0, outBuffer, 0, actuallyReceived);
return outBuffer;
}
return buffer;
}
/// <summary>
/// Initiates a control transfer over the default control endpoint. The request should have an IN direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter).
/// </summary>
/// <param name="requestType">The setup packet request type. The request type must specify the IN direction (highest bit set).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">The buffer that will receive the data transfered.</param>
/// <param name="length">Length of the data to transfer. The length member of the setup packet will be set to this length. The buffer specified
/// by the <paramref name="buffer"/> parameter should have at least this length.</param>
/// <returns>The number of bytes received from the device.</returns>
public int ControlIn(byte requestType, byte request, int value, int index, byte[] buffer, int length)
{
CheckIn(requestType);
return ControlTransfer(requestType, request, value, index, buffer, length);
}
/// <summary>
/// Initiates a control transfer over the default control endpoint. The request should have an IN direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter). The length of buffer given by the <paramref name="buffer"/> parameter will dictate
/// the number of bytes that are transfered and the value of the setup packet's length member.
/// </summary>
/// <param name="requestType">The setup packet request type. The request type must specify the IN direction (highest bit set).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">The buffer that will receive the data transfered. The length of this buffer will be the number of bytes transfered.</param>
/// <returns>The number of bytes received from the device.</returns>
public int ControlIn(byte requestType, byte request, int value, int index, byte[] buffer)
{
CheckIn(requestType);
return ControlTransfer(requestType, request, value, index, buffer);
}
/// <summary>
/// Initiates a control transfer without a data stage over the default control endpoint. The request should have an IN direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter). The setup packets' length member will be set to zero.
/// </summary>
/// <param name="requestType">The setup packet request type. The request type must specify the IN direction (highest bit set).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
public void ControlIn(byte requestType, byte request, int value, int index)
{
CheckIn(requestType);
// TODO: null instead of empty buffer. But overlapped code would have to be fixed for this (no buffer to pin)
ControlTransfer(requestType, request, value, index, []);
}
/// <summary>
/// Initiates a control transfer over the default control endpoint. The request should have an OUT direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter).
/// </summary>
/// <param name="requestType">The setup packet request type. The request type must specify the OUT direction (highest bit cleared).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">A buffer containing the data to transfer in the data stage.</param>
/// <param name="length">Length of the data to transfer. Only the first <paramref name="length"/> bytes of <paramref name="buffer"/> will be transfered.
/// The setup packet's length parameter is set to this length.</param>
public void ControlOut(byte requestType, byte request, int value, int index, byte[] buffer, int length)
{
CheckOut(requestType);
ControlTransfer(requestType, request, value, index, buffer, length);
}
/// <summary>
/// Initiates a control transfer over the default control endpoint. The request should have an OUT direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter).
/// </summary>
/// <param name="requestType">The setup packet request type. The request type must specify the OUT direction (highest bit cleared).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">A buffer containing the data to transfer in the data stage. The complete buffer is transfered. The setup packet's length
/// parameter is set to the length of this buffer.</param>
public void ControlOut(byte requestType, byte request, int value, int index, byte[] buffer)
{
CheckOut(requestType);
ControlTransfer(requestType, request, value, index, buffer);
}
/// <summary>
/// Initiates a control transfer without a data stage over the default control endpoint. The request should have an OUT direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter. The setup packets' length member will be set to zero.
/// </summary>
/// <param name="requestType">The setup packet request type. The request type must specify the OUT direction (highest bit cleared).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
public void ControlOut(byte requestType, byte request, int value, int index)
{
CheckOut(requestType);
// TODO: null instead of empty buffer. But overlapped code would have to be fixed for this (no buffer to pin)
ControlTransfer(requestType, request, value, index, []);
}
/// <summary>
/// Initiates an asynchronous control transfer without a data stage over the default control endpoint. This method allows both IN and OUT direction transfers, depending
/// on the highest bit of the <paramref name="requestType"/> parameter. Alternatively, <see cref="BeginControlIn(byte,byte,int,int,byte[],int,AsyncCallback,object)"/> and
/// <see cref="BeginControlIn(byte,byte,int,int,byte[],int,AsyncCallback,object)"/> can be used for asynchronous control transfers in a specific direction, which is
/// the recommended way because it prevents using the wrong direction accidentally. Use the BeginControlTransfer method when the direction is not
/// known at compile time. </summary>
/// <param name="requestType">The setup packet request type.</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the control transfer is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous control transfer, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndControlTransfer"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndControlTransfer"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginControlTransfer(byte requestType, byte request, int value, int index, AsyncCallback userCallback, object stateObject)
{
// TODO: null instead of empty buffer. But overlapped code would have to be fixed for this (no buffer to pin)
return BeginControlTransfer(requestType, request, value, index, [], 0, userCallback, stateObject);
}
/// <summary>
/// Initiates an asynchronous control transfer over the default control endpoint. The request should have an IN direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter).</summary>
/// <param name="requestType">The setup packet request type. The request type must specify the IN direction (highest bit set).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">The buffer that will receive the data transfered.</param>
/// <param name="length">Length of the data to transfer. Must be equal to or less than the length of <paramref name="buffer"/>. The setup packet's length member will be set to this length.</param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the control transfer is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous control transfer, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndControlTransfer"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndControlTransfer"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginControlIn(byte requestType, byte request, int value, int index, byte[] buffer, int length, AsyncCallback userCallback, object stateObject)
{
CheckIn(requestType);
return BeginControlTransfer(requestType, request, value, index, buffer, length, userCallback, stateObject);
}
/// <summary>
/// Initiates an asynchronous control transfer over the default control endpoint. The request should have an IN direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter).</summary>
/// <param name="requestType">The setup packet request type. The request type must specify the IN direction (highest bit set).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">The buffer that will receive the data transfered. The setup packet's length member will be set to the length of this buffer.</param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the control transfer is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous control transfer, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndControlTransfer"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndControlTransfer"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginControlIn(byte requestType, byte request, int value, int index, byte[] buffer, AsyncCallback userCallback, object stateObject)
{
CheckIn(requestType);
return BeginControlTransfer(requestType, request, value, index, buffer, userCallback, stateObject);
}
/// <summary>
/// Initiates an asynchronous control transfer without a data stage over the default control endpoint.
/// The request should have an IN direction (specified by the highest bit of the <paramref name="requestType"/> parameter).
/// The setup packets' length member will be set to zero.</summary>
/// <param name="requestType">The setup packet request type. The request type must specify the IN direction (highest bit set).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the control transfer is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous control transfer, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndControlTransfer"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndControlTransfer"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginControlIn(byte requestType, byte request, int value, int index, AsyncCallback userCallback, object stateObject)
{
CheckIn(requestType);
return BeginControlTransfer(requestType, request, value, index, userCallback, stateObject);
}
/// <summary>
/// Initiates an asynchronous control transfer over the default control endpoint. The request should have an OUT direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter).</summary>
/// <param name="requestType">The setup packet request type. The request type must specify the OUT direction (highest bit cleared).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">The buffer that contains the data to be transfered.</param>
/// <param name="length">Length of the data to transfer. Must be equal to or less than the length of <paramref name="buffer"/>. The setup packet's length member will be set to this length.</param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the control transfer is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous control transfer, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndControlTransfer"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndControlTransfer"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginControlOut(byte requestType, byte request, int value, int index, byte[] buffer, int length, AsyncCallback userCallback, object stateObject)
{
CheckOut(requestType);
return BeginControlTransfer(requestType, request, value, index, buffer, length, userCallback, stateObject);
}
/// <summary>
/// Initiates an asynchronous control transfer over the default control endpoint. The request should have an OUT direction (specified by the highest bit
/// of the <paramref name="requestType"/> parameter).</summary>
/// <param name="requestType">The setup packet request type. The request type must specify the OUT direction (highest bit cleared).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="buffer">The buffer that contains the data to be transfered. The setup packet's length member will be set to the length of this buffer.</param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the control transfer is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous control transfer, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndControlTransfer"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndControlTransfer"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginControlOut(byte requestType, byte request, int value, int index, byte[] buffer, AsyncCallback userCallback, object stateObject)
{
CheckOut(requestType);
return BeginControlTransfer(requestType, request, value, index, buffer, userCallback, stateObject);
}
/// <summary>
/// Initiates an asynchronous control transfer without a data stage over the default control endpoint.
/// The request should have an OUT direction (specified by the highest bit of the <paramref name="requestType"/> parameter).
/// The setup packets' length member will be set to zero.</summary>
/// <param name="requestType">The setup packet request type. The request type must specify the OUT direction (highest bit cleared).</param>
/// <param name="request">The setup packet device request.</param>
/// <param name="value">The value member in the setup packet. Its meaning depends on the request. Value should be between zero and 65535 (0xFFFF).</param>
/// <param name="index">The index member in the setup packet. Its meaning depends on the request. Index should be between zero and 65535 (0xFFFF).</param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the control transfer is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous control transfer, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndControlTransfer"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndControlTransfer"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginControlOut(byte requestType, byte request, int value, int index, AsyncCallback userCallback, object stateObject)
{
CheckOut(requestType);
// TODO: null instead of empty buffer. But overlapped code would have to be fixed for this (no buffer to pin)
return BeginControlTransfer(requestType, request, value, index, [], userCallback, stateObject);
}
private void CheckNotDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException("USB device object has been disposed.");
}
}
/// <summary>
/// Finds WinUSB devices with a GUID matching the parameter guidString
/// </summary>
/// <param name="guidString">The GUID string that the device should match.
/// The format of this string may be any format accepted by the constructor
/// of the System.Guid class</param>
/// <returns>An array of USBDeviceInfo objects representing the
/// devices found. When no devices are found an empty array is
/// returned.</returns>
public static USBDeviceInfo[] GetDevices(string guidString)
{
return GetDevices(new Guid(guidString));
}
/// <summary>
/// Finds WinUSB devices with a GUID matching the parameter guid
/// </summary>
/// <param name="guid">The GUID that the device should match.</param>
/// <returns>An array of USBDeviceInfo objects representing the
/// devices found. When no devices are found an empty array is
/// returned.</returns>
public static USBDeviceInfo[] GetDevices(Guid guid)
{
API.DeviceDetails[] detailList = API.DeviceManagement.FindDevicesFromGuid(guid);
USBDeviceInfo[] devices = new USBDeviceInfo[detailList.Length];
for (int i = 0; i < detailList.Length; i++)
{
devices[i] = new USBDeviceInfo(detailList[i]);
}
return devices;
}
/// <summary>
/// Finds the first WinUSB device with a GUID matching the parameter guid.
/// If multiple WinUSB devices match the GUID only the first one is returned.
/// </summary>
/// <param name="guid">The GUID that the device should match.</param>
/// <returns>An UsbDevice object representing the device if found. If
/// no device with the given GUID could be found null is returned.</returns>
public static USBDevice GetSingleDevice(Guid guid)
{
API.DeviceDetails[] detailList = API.DeviceManagement.FindDevicesFromGuid(guid);
if (detailList.Length == 0)
{
return null;
}
return new USBDevice(detailList[0].DevicePath);
}
/// <summary>
/// Finds the first WinUSB device with a GUID matching the parameter guidString.
/// If multiple WinUSB devices match the GUID only the first one is returned.
/// </summary>
/// <param name="guidString">The GUID string that the device should match.</param>
/// <returns>An UsbDevice object representing the device if found. If
/// no device with the given GUID could be found null is returned.</returns>
public static USBDevice GetSingleDevice(string guidString)
{
return GetSingleDevice(new Guid(guidString));
}
private static USBDeviceDescriptor GetDeviceDescriptor(string devicePath)
{
try
{
USBDeviceDescriptor descriptor;
using (API.WinUSBDevice wuDevice = new())
{
wuDevice.OpenDevice(devicePath);
API.USB_DEVICE_DESCRIPTOR deviceDesc = wuDevice.GetDeviceDescriptor();
// Get first supported language ID
ushort[] langIDs = wuDevice.GetSupportedLanguageIDs();
ushort langID = 0;
if (langIDs.Length > 0)
{
langID = langIDs[0];
}
string manufacturer = null, product = null, serialNumber = null;
byte idx = 0;
try
{
idx = deviceDesc.iManufacturer;
if (idx > 0)
{
manufacturer = wuDevice.GetStringDescriptor(idx, langID);
}
}
catch { }
try
{
idx = deviceDesc.iProduct;
if (idx > 0)
{
product = wuDevice.GetStringDescriptor(idx, langID);
}
}
catch { }
try
{
idx = deviceDesc.iSerialNumber;
if (idx > 0)
{
serialNumber = wuDevice.GetStringDescriptor(idx, langID);
}
}
catch { }
descriptor = new USBDeviceDescriptor(devicePath, deviceDesc, manufacturer, product, serialNumber);
}
return descriptor;
}
catch (API.APIException e)
{
throw new USBException("Failed to retrieve device descriptor.", e);
}
}
}
}
+132
View File
@@ -0,0 +1,132 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
namespace MadWizard.WinUSBNet
{
/// <summary>
/// USB device details
/// </summary>
public class USBDeviceDescriptor
{
/// <summary>
/// Windows path name for the USB device
/// </summary>
public string PathName { get; }
/// <summary>
/// USB vendor ID (VID) of the device
/// </summary>
public int VID { get; }
/// <summary>
/// USB product ID (PID) of the device
/// </summary>
public int PID { get; }
/// <summary>
/// Manufacturer name, or null if not available
/// </summary>
public string Manufacturer { get; }
/// <summary>
/// Product name, or null if not available
/// </summary>
public string Product { get; }
/// <summary>
/// Device serial number, or null if not available
/// </summary>
public string SerialNumber { get; }
/// <summary>
/// Friendly device name, or path name when no
/// further device information is available
/// </summary>
public string FullName
{
get
{
if (Manufacturer != null && Product != null)
{
return Product + " - " + Manufacturer;
}
else if (Product != null)
{
return Product;
}
else if (SerialNumber != null)
{
return SerialNumber;
}
else
{
return PathName;
}
}
}
/// <summary>
/// Device class code as defined in the interface descriptor
/// This property can be used if the class type is not defined
/// int the USBBaseClass enumeration
/// </summary>
public byte ClassValue
{
get;
}
/// <summary>
/// Device subclass code
/// </summary>
public byte SubClass
{
get;
}
/// <summary>
/// Device protocol code
/// </summary>
public byte Protocol
{
get;
}
/// <summary>
/// Device class code. If the device class does
/// not match any of the USBBaseClass enumeration values
/// the value will be USBBaseClass.Unknown
/// </summary>
public USBBaseClass BaseClass
{
get;
}
internal USBDeviceDescriptor(string path, API.USB_DEVICE_DESCRIPTOR deviceDesc, string manufacturer, string product, string serialNumber)
{
PathName = path;
VID = deviceDesc.idVendor;
PID = deviceDesc.idProduct;
Manufacturer = manufacturer;
Product = product;
SerialNumber = serialNumber;
ClassValue = deviceDesc.bDeviceClass;
SubClass = deviceDesc.bDeviceSubClass;
Protocol = deviceDesc.bDeviceProtocol;
// If interface class is of a known type (USBBaseeClass enum), use this
// for the InterfaceClass property.
BaseClass = USBBaseClass.Unknown;
if (Enum.IsDefined(typeof(USBBaseClass), (int)deviceDesc.bDeviceClass))
{
BaseClass = (USBBaseClass)deviceDesc.bDeviceClass;
}
}
}
}
+91
View File
@@ -0,0 +1,91 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
namespace MadWizard.WinUSBNet
{
/// <summary>
/// Gives information about a device. This information is retrieved using the setup API, not the
/// actual device descriptor. Device description and manufacturer will be the strings specified
/// in the .inf file. After a device is opened the actual device descriptor can be read as well.
/// </summary>
public class USBDeviceInfo
{
private API.DeviceDetails _details;
/// <summary>
/// Vendor ID (VID) of the USB device
/// </summary>
public int VID
{
get
{
return _details.VID;
}
}
/// <summary>
/// Product ID (VID) of the USB device
/// </summary>
public int PID
{
get
{
return _details.PID;
}
}
/// <summary>
/// Manufacturer of the device, as specified in the INF file (not the device descriptor)
/// </summary>
public string Manufacturer
{
get
{
return _details.Manufacturer;
}
}
/// <summary>
/// Description of the device, as specified in the INF file (not the device descriptor)
/// </summary>
public string DeviceDescription
{
get
{
return _details.DeviceDescription;
}
}
/// <summary>
/// Device pathname
/// </summary>
public string DevicePath
{
get
{
return _details.DevicePath;
}
}
/// <summary>
/// BusName
/// Heathcliff74
/// </summary>
public string BusName
{
get
{
return _details.BusName;
}
}
internal USBDeviceInfo(API.DeviceDetails details)
{
_details = details;
}
}
}
+42
View File
@@ -0,0 +1,42 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
namespace MadWizard.WinUSBNet
{
/// <summary>
/// Exception used by WinUSBNet to indicate errors. This is the
/// main exception to catch when using the library.
/// </summary>
public class USBException : Exception
{
/// <summary>
/// Constructs a new USBException with the given message
/// </summary>
/// <param name="message">The message describing the exception</param>
public USBException(string message)
: base(message)
{
}
/// <summary>
/// Constructs a new USBException with the given message and underlying exception
/// that caused the USBException.
/// </summary>
/// <param name="message">The message describing the exception</param>
/// <param name="innerException">The underlying exception causing the USBException</param>
public USBException(string message, Exception innerException)
: base(message, innerException)
{
}
public USBException() : base()
{
}
}
}
+144
View File
@@ -0,0 +1,144 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
namespace MadWizard.WinUSBNet
{
/// <summary>
/// Represents a single USB interface from a USB device
/// </summary>
public class USBInterface
{
/// <summary>
/// Collection of pipes associated with this interface
/// </summary>
public USBPipeCollection Pipes
{
get;
}
/// <summary>
/// Interface number from the interface descriptor
/// </summary>
public int Number
{
get;
}
/// <summary>
/// USB device associated with this interface
/// </summary>
public USBDevice Device
{
get;
}
/// <summary>
/// First IN direction pipe on this interface
/// </summary>
public USBPipe InPipe
{
get;
}
/// <summary>
/// First OUT direction pipe on this interface
/// </summary>
public USBPipe OutPipe
{
get;
}
/// <summary>
/// Interface class code. If the interface class does
/// not match any of the USBBaseClass enumeration values
/// the value will be USBBaseClass.Unknown
/// </summary>
public USBBaseClass BaseClass
{
get;
}
/// <summary>
/// Interface class code as defined in the interface descriptor
/// This property can be used if the class type is not defined
/// int the USBBaseClass enumeration
/// </summary>
public byte ClassValue
{
get;
}
/// <summary>
/// Interface subclass code
/// </summary>
public byte SubClass
{
get;
}
/// <summary>
/// Interface protocol code
/// </summary>
public byte Protocol
{
get;
}
/// <summary>
/// Zero based interface index in WinUSB.
/// Note that this is not necessarily the same as the interface *number*
/// from the interface descriptor. There might be interfaces within the
/// USB device that do not use WinUSB, these are not counted for index.
/// </summary>
internal int InterfaceIndex
{
get;
}
internal USBInterface(USBDevice device, int interfaceIndex, API.USB_INTERFACE_DESCRIPTOR rawDesc, USBPipeCollection pipes)
{
// Set raw class identifiers
ClassValue = rawDesc.bInterfaceClass;
SubClass = rawDesc.bInterfaceSubClass;
Protocol = rawDesc.bInterfaceProtocol;
Number = rawDesc.bInterfaceNumber;
InterfaceIndex = interfaceIndex;
// If interface class is of a known type (USBBaseClass enum), use this
// for the InterfaceClass property.
BaseClass = USBBaseClass.Unknown;
if (Enum.IsDefined(typeof(USBBaseClass), (int)rawDesc.bInterfaceClass))
{
BaseClass = (USBBaseClass)rawDesc.bInterfaceClass;
}
Device = device;
Pipes = pipes;
// Handle pipes
foreach (USBPipe pipe in pipes)
{
// Attach pipe to this interface
pipe.AttachInterface(this);
// If first in or out pipe, set InPipe and OutPipe
if (pipe.IsIn && InPipe == null)
{
InPipe = pipe;
}
if (pipe.IsOut && OutPipe == null)
{
OutPipe = pipe;
}
}
}
}
}
+163
View File
@@ -0,0 +1,163 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
using System.Collections;
using System.Collections.Generic;
namespace MadWizard.WinUSBNet
{
/// <summary>
/// Collection of UsbInterface objects
/// </summary>
public class USBInterfaceCollection : IEnumerable<USBInterface>
{
private readonly USBInterface[] _interfaces;
internal USBInterfaceCollection(USBInterface[] interfaces)
{
_interfaces = interfaces;
}
private class USBInterfaceEnumerator : IEnumerator<USBInterface>
{
private int _index;
private readonly USBInterface[] _interfaces;
public USBInterfaceEnumerator(USBInterface[] interfaces)
{
_interfaces = interfaces;
_index = -1;
}
public void Dispose()
{
// Intentionally empty
}
private USBInterface GetCurrent()
{
try
{
return _interfaces[_index];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
public USBInterface Current
{
get
{
return GetCurrent();
}
}
object IEnumerator.Current
{
get
{
return GetCurrent();
}
}
public bool MoveNext()
{
_index++;
return _index < _interfaces.Length;
}
public void Reset()
{
_index = -1;
}
}
/// <summary>
/// Finds the first interface with that matches the device class
/// given by the <paramref name="interfaceClass"/> parameter.
/// </summary>
/// <param name="interfaceClass">The device class the interface should match</param>
/// <returns>The first interface with the given interface class, or null
/// if no such interface exists.</returns>
public USBInterface Find(USBBaseClass interfaceClass)
{
for (int i = 0; i < _interfaces.Length; i++)
{
USBInterface iface = _interfaces[i];
if (iface.BaseClass == interfaceClass)
{
return iface;
}
}
return null;
}
/// <summary>
/// Finds all interfaces matching the device class given by the
/// <paramref name="interfaceClass"/> parameter.
/// </summary>
/// <param name="interfaceClass">The device class the interface should match</param>
/// <returns>An array of USBInterface objects matching the device class, or an empty
/// array if no interface matches.</returns>
public USBInterface[] FindAll(USBBaseClass interfaceClass)
{
List<USBInterface> matchingInterfaces = [];
for (int i = 0; i < _interfaces.Length; i++)
{
USBInterface iface = _interfaces[i];
if (iface.BaseClass == interfaceClass)
{
matchingInterfaces.Add(iface);
}
}
return [.. matchingInterfaces];
}
/// <summary>
/// Returns a typed enumerator that iterates through a collection.
/// </summary>
/// <returns>The enumerator object that can be used to iterate through the collection.</returns>
public IEnumerator<USBInterface> GetEnumerator()
{
return new USBInterfaceEnumerator(_interfaces);
}
/// <summary>
/// Get interface by interface number
/// </summary>
/// <param name="interfaceNumber">Number of the interface to return. Note: this is the number from the interface descriptor, which
/// is not necessarily the same as the interface index.</param>
/// <exception cref="IndexOutOfRangeException">Thrown when the given interface number does not exist in the collection.</exception>
/// <returns></returns>
public USBInterface this[int interfaceNumber]
{
get
{
for (int i = 0; i < _interfaces.Length; i++)
{
USBInterface iface = _interfaces[i];
if (iface.Number == interfaceNumber)
{
return iface;
}
}
throw new IndexOutOfRangeException(string.Format("No interface with number {0} exists.", interfaceNumber));
}
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An IEnumerator object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return new USBInterfaceEnumerator(_interfaces);
}
}
}
+240
View File
@@ -0,0 +1,240 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
namespace MadWizard.WinUSBNet
{
/// <summary>
/// Delegate for event handler methods handing USB events
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">Details of the event</param>
public delegate void USBEventHandler(object sender, USBEvent e);
/// <summary>
/// Event type enumeration for WinUSB events
/// </summary>
public enum USBEventType
{
/// <summary>
/// A device has been connected to the system
/// </summary>
DeviceArrival,
/// <summary>
/// A device has been disconnected from the system
/// </summary>
DeviceRemoval,
}
/// <summary>
/// Contains the details of a USB event
/// </summary>
public class USBEvent : EventArgs
{
/// <summary>
/// WinUSB interface GUID of the device as specified in the WinUSBNotifier
/// </summary>
public Guid Guid;
/// <summary>
/// Device pathname that identifies the device
/// </summary>
public string DevicePath;
/// <summary>
/// Type of event that occurred
/// </summary>
public USBEventType Type;
internal USBEvent(USBEventType type, Guid guid, string devicePath)
{
this.Guid = guid;
this.DevicePath = devicePath;
this.Type = type;
}
}
/// <summary>
/// Helper class to receive notifications on USB device changes such as
/// connecting or removing a device.
/// </summary>
public class USBNotifier : IDisposable
{
private readonly DeviceNotifyHook _hook;
private Guid _guid;
private readonly WPinternals.HelperClasses.AsyncAutoResetEvent NodeChangeEvent = new(false);
/// <summary>
/// Event triggered when a new USB device that matches the USBNotifier's GUID is connected
/// </summary>
private event EventHandler<USBEvent> _Arrival;
public event EventHandler<USBEvent> Arrival
{
// Heathcliff74 - Also notify currently connected USB devices
add
{
_Arrival -= value;
_Arrival += value;
foreach (USBDeviceInfo Device in USBDevice.GetDevices(Guid))
{
_Arrival(this, new USBEvent(USBEventType.DeviceArrival, Guid, Device.DevicePath));
}
}
remove
{
_Arrival -= value;
}
}
/// <summary>
/// Event triggered when a new USB device that matches the USBNotifier's GUID is disconnected
/// </summary>
public event EventHandler<USBEvent> Removal;
/// <summary>
/// The interface GUID of devices this USBNotifier will watch
/// </summary>
public Guid Guid
{
get
{
return _guid;
}
}
/// <summary>
/// Constructs a new USBNotifier that will watch for events on
/// devices matching the given interface GUID. A Windows Forms control
/// is needed since the notifier relies on window messages.
/// </summary>
/// <param name="control">A control that will be used internally for device notification messages.
/// You can use a Form object for example.</param>
/// <param name="guidString">The interface GUID string of the devices to watch.</param>
public USBNotifier(string guidString) :
this(new Guid(guidString))
{
// Handled in other constructor
}
/// <summary>
/// Constructs a new USBNotifier that will watch for events on
/// devices matching the given interface GUID. A Windows Forms control
/// is needed since the notifier relies on window messages.
/// </summary>
/// <param name="control">A control that will be used internally for device notification messages.
/// You can use a Form object for example.</param>
/// <param name="guid">The interface GUID of the devices to watch.</param>
public USBNotifier(Guid guid)
{
_guid = guid;
_hook = new DeviceNotifyHook(this, _guid);
}
/// <summary>
/// Triggers the arrival event
/// </summary>
/// <param name="devicePath">Device pathname of the device that has been connected</param>
protected void OnArrival(string devicePath)
{
_Arrival?.Invoke(this, new USBEvent(USBEventType.DeviceArrival, _guid, devicePath));
}
/// <summary>
/// Triggers the removal event
/// </summary>
/// <param name="devicePath">Device pathname of the device that has been connected</param>
protected void OnRemoval(string devicePath)
{
Removal?.Invoke(this, new USBEvent(USBEventType.DeviceRemoval, _guid, devicePath));
}
internal int HandleDeviceChange(int msg, IntPtr wParam, IntPtr lParam)
{
if (msg != API.DeviceManagement.WM_DEVICECHANGE)
{
throw new USBException("Invalid device change message."); // should not happen
}
//switch ((int)wParam)
//{
// case API.DeviceManagement.DBT_DEVICEARRIVAL:
// WPinternals.LogFile.Log(Guid.ToString() + " - DBT_DEVICEARRIVAL", WPinternals.LogType.FileOnly);
// break;
// case API.DeviceManagement.DBT_DEVICEREMOVECOMPLETE:
// WPinternals.LogFile.Log(Guid.ToString() + " - DBT_DEVICEREMOVECOMPLETE", WPinternals.LogType.FileOnly);
// break;
// case API.DeviceManagement.DBT_DEVNODES_CHANGED:
// WPinternals.LogFile.Log(Guid.ToString() + " - DBT_DEVNODES_CHANGED", WPinternals.LogType.FileOnly);
// break;
// case API.DeviceManagement.DBT_QUERYCHANGECONFIG:
// WPinternals.LogFile.Log(Guid.ToString() + " - DBT_QUERYCHANGECONFIG", WPinternals.LogType.FileOnly);
// break;
// default:
// WPinternals.LogFile.Log(Guid.ToString() + " - wParam: 0x" + ((int)wParam).ToString("X8"), WPinternals.LogType.FileOnly);
// break;
//}
if ((int)wParam == API.DeviceManagement.DBT_DEVICEARRIVAL)
{
string devName = API.DeviceManagement.GetNotifyMessageDeviceName(lParam, _guid);
if (devName != null)
{
OnArrival(devName);
}
}
if ((int)wParam == API.DeviceManagement.DBT_DEVICEREMOVECOMPLETE)
{
string devName = API.DeviceManagement.GetNotifyMessageDeviceName(lParam, _guid);
if (devName != null)
{
OnRemoval(devName);
}
}
if ((int)wParam == API.DeviceManagement.DBT_DEVNODES_CHANGED)
{
NodeChangeEvent.Set();
}
if ((int)wParam == API.DeviceManagement.DBT_QUERYCHANGECONFIG)
{
return 1; // Give permission
}
return 0;
}
public async System.Threading.Tasks.Task WaitForNextNodeChange()
{
await NodeChangeEvent.WaitAsync(System.Threading.Timeout.InfiniteTimeSpan);
}
/// <summary>
/// Disposes the USBNotifier object and frees all resources.
/// Call this method when the object is no longer needed.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the object's resources.
/// </summary>
/// <param name="disposing">True when dispose is called manually, false when called by the finalizer.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_hook.Dispose();
}
}
}
}
+491
View File
@@ -0,0 +1,491 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
namespace MadWizard.WinUSBNet
{
/// <summary>
/// UsbPipe represents a single pipe on a WinUSB device. A pipe is connected
/// to a certain endpoint on the device and has a fixed direction (IN or OUT)
/// </summary>
public class USBPipe
{
private API.WINUSB_PIPE_INFORMATION _pipeInfo;
private USBInterface _interface = null;
private USBPipePolicy _policy;
private byte[] LastWritten = null;
/// <summary>
/// Endpoint address including the direction in the most significant bit
/// </summary>
public byte Address
{
get
{
return _pipeInfo.PipeId;
}
}
/// <summary>
/// The USBDevice this pipe is associated with
/// </summary>
public USBDevice Device { get; }
/// <summary>
/// Maximum packet size for transfers on this endpoint
/// </summary>
public int MaximumPacketSize
{
get
{
return _pipeInfo.MaximumPacketSize;
}
}
/// <summary>
/// The interface associated with this pipe
/// </summary>
public USBInterface Interface
{
get
{
return _interface;
}
}
/// <summary>
/// The pipe policy settings for this pipe
/// </summary>
public USBPipePolicy Policy
{
get
{
return _policy;
}
}
/// <summary>
/// True if the pipe has direction OUT (host to device), false otherwise.
/// </summary>
public bool IsOut
{
get
{
return (_pipeInfo.PipeId & 0x80) == 0;
}
}
/// <summary>
/// True if the pipe has direction IN (device to host), false otherwise.
/// </summary>
public bool IsIn
{
get
{
return (_pipeInfo.PipeId & 0x80) != 0;
}
}
/// <summary>
/// Reads data from the pipe into a buffer.
/// </summary>
/// <param name="buffer">The buffer to read data into. The maximum number of bytes that will be read is specified by the length of the buffer.</param>
/// <returns>The number of bytes read from the pipe.</returns>
public int Read(byte[] buffer)
{
return Read(buffer, 0, buffer.Length);
}
/// <summary>
/// Reads data from the pipe into a buffer.
/// </summary>
/// <param name="buffer">The buffer to read data into.</param>
/// <param name="offset">The byte offset in <paramref name="buffer"/> from which to begin writing data read from the pipe.</param>
/// <param name="length">The maximum number of bytes to read, starting at offset</param>
/// <returns>The number of bytes read from the pipe.</returns>
public int Read(byte[] buffer, int offset, int length)
{
CheckReadParams(buffer, offset, length);
try
{
Device.InternalDevice.ReadPipe(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length, out uint bytesRead);
return (int)bytesRead;
}
catch (API.APIException e)
{
// throw new USBException("Failed to read from pipe.", e);
LogAndThrowException(new USBException("Failed to read from pipe.", e));
return 0;
}
}
private void CheckReadParams(byte[] buffer, int offset, int length)
{
if (!IsIn)
{
// throw new ArgumentOutOfRangeException("Offset of data to read is outside the buffer boundaries.");
LogAndThrowException(new ArgumentOutOfRangeException("Offset of data to read is outside the buffer boundaries."));
}
int bufferLength = buffer.Length;
if (offset < 0 || offset >= bufferLength)
{
// throw new ArgumentOutOfRangeException(nameof(offset), "Offset of data to read is outside the buffer boundaries.");
LogAndThrowException(new ArgumentOutOfRangeException("Offset of data to read is outside the buffer boundaries."));
}
if (length < 0 || (offset + length) > bufferLength)
{
// throw new ArgumentOutOfRangeException(nameof(length), "Length of data to read is outside the buffer boundaries.");
LogAndThrowException(new ArgumentOutOfRangeException("Length of data to read is outside the buffer boundaries."));
}
}
private void CheckWriteParams(byte[] buffer, int offset, int length)
{
if (!IsOut)
{
//throw new NotSupportedException("Cannot write to a pipe with IN direction.");
LogAndThrowException(new NotSupportedException("Cannot write to a pipe with IN direction."));
}
int bufferLength = buffer.Length;
if (offset < 0 || offset >= bufferLength)
{
// throw new ArgumentOutOfRangeException(nameof(offset), "Offset of data to write is outside the buffer boundaries.");
LogAndThrowException(new ArgumentOutOfRangeException("Offset of data to write is outside the buffer boundaries."));
}
if (length < 0 || (offset + length) > bufferLength)
{
// throw new ArgumentOutOfRangeException(nameof(length), "Length of data to write is outside the buffer boundaries.");
LogAndThrowException(new ArgumentOutOfRangeException("Length of data to write is outside the buffer boundaries."));
}
}
/// <summary>Initiates an asynchronous read operation on the pipe. </summary>
/// <param name="buffer">Buffer that will receive the data read from the pipe.</param>
/// <param name="offset">Byte offset within the buffer at which to begin writing the data received.</param>
/// <param name="length">Length of the data to transfer.</param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the operation is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous operation, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndRead"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndRead"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginRead(byte[] buffer, int offset, int length, AsyncCallback userCallback, object stateObject)
{
CheckReadParams(buffer, offset, length);
USBAsyncResult result = new(userCallback, stateObject);
try
{
Device.InternalDevice.ReadPipeOverlapped(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length, result);
}
catch (API.APIException e)
{
result.Dispose();
//throw new USBException("Failed to read from pipe.", e);
LogAndThrowException(new USBException("Failed to read from pipe.", e));
}
catch (Exception e)
{
result?.Dispose();
// throw;
LogException(e);
throw;
}
return result;
}
/// <summary>
/// Waits for a pending asynchronous read operation to complete.
/// </summary>
/// <param name="asyncResult">The <see cref="IAsyncResult"/> object representing the asynchronous operation,
/// as returned by <see cref="BeginRead"/>.</param>
/// <returns>The number of bytes transfered during the operation.</returns>
/// <remarks>Every call to <see cref="BeginRead"/> must have a matching call to <see cref="EndRead"/> to dispose
/// of any resources used and to retrieve the result of the operation. When the operation was successful the method returns the number
/// of bytes that were transfered. If an error occurred during the operation this method will throw the exceptions that would
/// otherwise have occurred during the operation. If the operation is not yet finished EndWrite will wait for the
/// operation to finish before returning.</remarks>
public int EndRead(IAsyncResult asyncResult)
{
if (asyncResult == null)
{
// throw new NullReferenceException("asyncResult cannot be null");
LogAndThrowException(new NullReferenceException("asyncResult cannot be null"));
}
if (!(asyncResult is USBAsyncResult))
{
// throw new ArgumentException("AsyncResult object was not created by calling BeginRead on this class.");
LogAndThrowException(new ArgumentException("AsyncResult object was not created by calling BeginRead on this class."));
}
// todo: check duplicate end reads?
USBAsyncResult result = (USBAsyncResult)asyncResult;
try
{
if (!result.IsCompleted)
{
result.AsyncWaitHandle.WaitOne();
}
if (result.Error != null)
{
// throw new USBException("Asynchronous read from pipe has failed.", result.Error);
LogAndThrowException(new USBException("Asynchronous read from pipe has failed.", result.Error));
}
return result.BytesTransfered;
}
finally
{
result.Dispose();
}
}
/// <summary>
/// Writes data from a buffer to the pipe.
/// </summary>
/// <param name="buffer">The buffer to write data from. The complete buffer will be written to the device.</param>
public void Write(byte[] buffer)
{
Write(buffer, 0, buffer.Length);
}
/// <summary>
/// Writes data from a buffer to the pipe.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="offset">The byte offset in <paramref name="buffer"/> from which to begin writing.</param>
/// <param name="length">The number of bytes to write, starting at offset</param>
public void Write(byte[] buffer, int offset, int length)
{
CheckWriteParams(buffer, offset, length);
LogLastWrite(buffer, offset, length);
try
{
Device.InternalDevice.WritePipe(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length);
}
catch (API.APIException e)
{
byte[] SubBuffer = new byte[length < 16 ? length : 16];
Array.Copy(buffer, offset, SubBuffer, 0, length < 16 ? length : 16);
// throw new USBException("Failed to write to pipe: " + WPinternals.Converter.ConvertHexToString(SubBuffer, ""), e);
LogAndThrowException(new USBException("Failed to write to pipe: " + WPinternals.HelperClasses.Converter.ConvertHexToString(SubBuffer, ""), e));
}
}
/// <summary>Initiates an asynchronous write operation on the pipe. </summary>
/// <param name="buffer">Buffer that contains the data to write to the pipe.</param>
/// <param name="offset">Byte offset within the buffer from which to begin writing.</param>
/// <param name="length">Length of the data to transfer.</param>
/// <param name="userCallback">An optional asynchronous callback, to be called when the operation is complete. Can be null if no callback is required.</param>
/// <param name="stateObject">A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required.</param>
/// <returns>An <see cref="IAsyncResult"/> object representing the asynchronous operation, which could still be pending.</returns>
/// <remarks>This method always completes immediately even if the operation is still pending. The <see cref="IAsyncResult"/> object returned represents the operation
/// and must be passed to <see cref="EndWrite"/> to retrieve the result of the operation. For every call to this method a matching call to
/// <see cref="EndWrite"/> must be made. When <paramref name="userCallback"/> specifies a callback function, this function will be called when the operation is completed. The optional
/// <paramref name="stateObject"/> parameter can be used to pass user-defined information to this callback or the <see cref="IAsyncResult"/>. The <see cref="IAsyncResult"/>
/// also provides an event handle (<see cref="IAsyncResult.AsyncWaitHandle" />) that will be triggered when the operation is complete as well.
/// </remarks>
public IAsyncResult BeginWrite(byte[] buffer, int offset, int length, AsyncCallback userCallback, object stateObject)
{
CheckWriteParams(buffer, offset, length);
LogLastWrite(buffer, offset, length);
USBAsyncResult result = new(userCallback, stateObject);
try
{
Device.InternalDevice.WriteOverlapped(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length, result);
}
catch (API.APIException e)
{
result?.Dispose();
byte[] SubBuffer = new byte[length < 16 ? length : 16];
Array.Copy(buffer, offset, SubBuffer, 0, length < 16 ? length : 16);
// throw new USBException("Failed to write to pipe: " + WPinternals.Converter.ConvertHexToString(SubBuffer, ""), e);
LogAndThrowException(new USBException("Failed to write to pipe: " + WPinternals.HelperClasses.Converter.ConvertHexToString(SubBuffer, ""), e));
}
catch (Exception e)
{
result?.Dispose();
// throw;
LogException(e);
throw;
}
return result;
}
/// <summary>
/// Waits for a pending asynchronous write operation to complete.
/// </summary>
/// <param name="asyncResult">The <see cref="IAsyncResult"/> object representing the asynchronous operation,
/// as returned by <see cref="BeginWrite"/>.</param>
/// <returns>The number of bytes transfered during the operation.</returns>
/// <remarks>Every call to <see cref="BeginWrite"/> must have a matching call to <see cref="EndWrite"/> to dispose
/// of any resources used and to retrieve the result of the operation. When the operation was successful the method returns the number
/// of bytes that were transfered. If an error occurred during the operation this method will throw the exceptions that would
/// otherwise have occurred during the operation. If the operation is not yet finished EndWrite will wait for the
/// operation to finish before returning.</remarks>
public void EndWrite(IAsyncResult asyncResult)
{
if (asyncResult == null)
{
// throw new NullReferenceException("asyncResult cannot be null");
LogAndThrowException(new NullReferenceException("asyncResult cannot be null"));
}
if (!(asyncResult is USBAsyncResult))
{
// throw new ArgumentException("AsyncResult object was not created by calling BeginWrite on this class.");
LogAndThrowException(new ArgumentException("AsyncResult object was not created by calling BeginWrite on this class."));
}
USBAsyncResult result = (USBAsyncResult)asyncResult;
try
{
// todo: check duplicate end writes?
if (!result.IsCompleted)
{
result.AsyncWaitHandle.WaitOne();
}
if (result.Error != null)
{
// throw new USBException("Asynchronous write to pipe has failed.", result.Error);
LogAndThrowException(new USBException("Asynchronous write to pipe has failed.", result.Error));
}
}
finally
{
result.Dispose();
}
}
/// <summary>
/// Aborts all pending transfers for this pipe.
/// </summary>
public void Abort()
{
try
{
Device.InternalDevice.AbortPipe(Interface.InterfaceIndex, _pipeInfo.PipeId);
}
catch (API.APIException e)
{
// throw new USBException("Failed to abort pipe.", e);
LogAndThrowException(new USBException("Failed to abort pipe.", e));
}
}
/// <summary>
/// Resets all pending transfers for this pipe.
/// </summary>
public void Reset()
{
try
{
Device.InternalDevice.ResetPipe(Interface.InterfaceIndex, _pipeInfo.PipeId);
}
catch (API.APIException e)
{
// throw new USBException("Failed to reset pipe.", e);
LogAndThrowException(new USBException("Failed to reset pipe.", e));
}
}
/// <summary>
/// Flushes the pipe, discarding any data that is cached. Only available on IN direction pipes.
/// </summary>
public void Flush()
{
if (!IsIn)
{
throw new NotSupportedException("Flush is only supported on IN direction pipes");
}
try
{
Device.InternalDevice.FlushPipe(Interface.InterfaceIndex, _pipeInfo.PipeId);
}
catch (API.APIException e)
{
// throw new USBException("Failed to flush pipe.", e);
LogAndThrowException(new USBException("Failed to flush pipe.", e));
}
}
internal USBPipe(USBDevice device, API.WINUSB_PIPE_INFORMATION pipeInfo)
{
_pipeInfo = pipeInfo;
Device = device;
// Policy is not set until interface is attached
_policy = null;
}
internal void AttachInterface(USBInterface usbInterface)
{
_interface = usbInterface;
// Initialize policy now that interface is set (policy requires interface)
_policy = new USBPipePolicy(Device, _interface.InterfaceIndex, _pipeInfo.PipeId);
}
private void LogException(Exception Ex)
{
// TODO: FIX!
//WPinternals.HelperClasses.LogFile.Log("Error on USB port!", WPinternals.HelperClasses.LogType.FileOnly);
//WPinternals.HelperClasses.LogFile.Log("Device: " + Device.Descriptor.FullName, WPinternals.HelperClasses.LogType.FileOnly);
if (IsIn)
{
LastWritten = Device.OutputPipe.LastWritten;
}
if ((LastWritten == null) && (Ex is USBException) && (Ex.InnerException is API.APIException) &&
(((API.APIException)Ex.InnerException).InnerException is System.ComponentModel.Win32Exception) &&
(((System.ComponentModel.Win32Exception)Ex.InnerException.InnerException).NativeErrorCode == 0X1F))
{
//WPinternals.HelperClasses.LogFile.Log("Failed to communicate on new USB connection", WPinternals.HelperClasses.LogType.FileAndConsole);
}
if (LastWritten != null)
{
//WPinternals.HelperClasses.LogFile.Log("Last written: " + WPinternals.HelperClasses.Converter.ConvertHexToString(LastWritten, ""), WPinternals.HelperClasses.LogType.FileOnly);
}
//WPinternals.HelperClasses.LogFile.LogException(Ex, WPinternals.HelperClasses.LogType.FileOnly);
}
private void LogAndThrowException(Exception Ex)
{
LogException(Ex);
throw Ex;
}
private void LogLastWrite(byte[] Buffer, int Offset, int Length)
{
LastWritten = new byte[Length < 0x10 ? Length : 0x10];
System.Buffer.BlockCopy(Buffer, Offset, LastWritten, 0, LastWritten.Length);
}
}
}
+136
View File
@@ -0,0 +1,136 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
using System.Collections;
using System.Collections.Generic;
namespace MadWizard.WinUSBNet
{
/// <summary>
/// Collection of UsbPipe objects
/// </summary>
public class USBPipeCollection : IEnumerable<USBPipe>
{
private readonly Dictionary<byte, USBPipe> _pipes;
internal USBPipeCollection(USBPipe[] pipes)
{
_pipes = new Dictionary<byte, USBPipe>(pipes.Length);
foreach (USBPipe pipe in pipes)
{
if (_pipes.ContainsKey(pipe.Address))
{
throw new USBException("Duplicate pipe address in endpoint.");
}
_pipes[pipe.Address] = pipe;
}
}
/// <summary>
/// Returns the pipe from the collection with the given pipe address
/// </summary>
/// <param name="pipeAddress">Address of the pipe to return</param>
/// <returns>The pipe with the given pipe address</returns>
/// <exception cref="IndexOutOfRangeException">Thrown if no pipe with the specified address
/// is available in the collection.</exception>
public USBPipe this[byte pipeAddress]
{
get
{
if (!_pipes.TryGetValue(pipeAddress, out USBPipe pipe))
{
throw new IndexOutOfRangeException();
}
return pipe;
}
}
private class UsbPipeEnumerator : IEnumerator<USBPipe>
{
private int _index;
private readonly USBPipe[] _pipes;
public UsbPipeEnumerator(USBPipe[] pipes)
{
_pipes = pipes;
_index = -1;
}
public void Dispose()
{
// Empty
}
private USBPipe GetCurrent()
{
try
{
return _pipes[_index];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
public USBPipe Current
{
get
{
return GetCurrent();
}
}
object IEnumerator.Current
{
get
{
return GetCurrent();
}
}
public bool MoveNext()
{
_index++;
return _index < _pipes.Length;
}
public void Reset()
{
_index = -1;
}
}
private USBPipe[] GetPipeList()
{
var values = _pipes.Values;
USBPipe[] pipeList = new USBPipe[values.Count];
values.CopyTo(pipeList, 0);
return pipeList;
}
/// <summary>
/// Returns a typed enumerator that iterates through a collection.
/// </summary>
/// <returns>The enumerator object that can be used to iterate through the collection.</returns>
public IEnumerator<USBPipe> GetEnumerator()
{
return new UsbPipeEnumerator(GetPipeList());
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An IEnumerator object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return new UsbPipeEnumerator(GetPipeList());
}
}
}
+180
View File
@@ -0,0 +1,180 @@
/* WinUSBNet library
* (C) 2010 Thomas Bleeker (www.madwizard.org)
*
* Licensed under the MIT license, see license.txt or:
* http://www.opensource.org/licenses/mit-license.php
*/
using System;
namespace MadWizard.WinUSBNet
{
/// <summary>
/// Describes the policy for a specific USB pipe
/// </summary>
public class USBPipePolicy
{
private readonly byte _pipeID;
private readonly int _interfaceIndex;
private readonly USBDevice _device;
internal USBPipePolicy(USBDevice device, int interfaceIndex, byte pipeID)
{
_pipeID = pipeID;
_interfaceIndex = interfaceIndex;
_device = device;
}
private void RequireDirectionOut()
{
// Some policy types only apply specifically to OUT direction pipes
if ((_pipeID & 0x80) != 0)
{
throw new NotSupportedException("This policy type is only allowed on OUT direction pipes.");
}
}
private void RequireDirectionIn()
{
// Some policy types only apply specifically to IN direction pipes
// This function checks for this.
if ((_pipeID & 0x80) == 0)
{
throw new NotSupportedException("This policy type is only allowed on IN direction pipes.");
}
}
/// <summary>
/// When false, read requests fail when the device returns more data than requested. When true, extra data is
/// saved and returned on the next read. Default value is true. Only available on IN direction pipes.
/// </summary>
/// <seealso href="http://msdn.microsoft.com/en-us/library/aa476439.aspx">WinUSB_GetPipePolicy for a more detailed description</seealso>
public bool AllowPartialReads
{
get
{
RequireDirectionIn();
return _device.InternalDevice.GetPipePolicyBool(_interfaceIndex, _pipeID, API.POLICY_TYPE.ALLOW_PARTIAL_READS);
}
set
{
RequireDirectionIn();
_device.InternalDevice.SetPipePolicy(_interfaceIndex, _pipeID, API.POLICY_TYPE.ALLOW_PARTIAL_READS, value);
}
}
/// <summary>
/// When true, the driver fails stalled data transfers, but the driver clears the stall condition automatically. Default
/// value is false.
/// </summary>
/// <seealso href="http://msdn.microsoft.com/en-us/library/aa476439.aspx">WinUSB_GetPipePolicy for a more detailed description</seealso>
public bool AutoClearStall
{
get
{
return _device.InternalDevice.GetPipePolicyBool(_interfaceIndex, _pipeID, API.POLICY_TYPE.AUTO_CLEAR_STALL);
}
set
{
_device.InternalDevice.SetPipePolicy(_interfaceIndex, _pipeID, API.POLICY_TYPE.AUTO_CLEAR_STALL, value);
}
}
/// <summary>
/// If both AllowPartialReads and AutoFlush are true, when the device returns more data than requested by the client it
/// will discard the remaining data. Default value is false. Only available on IN direction pipes.
/// </summary>
/// <seealso href="http://msdn.microsoft.com/en-us/library/aa476439.aspx">WinUSB_GetPipePolicy for a more detailed description</seealso>
public bool AutoFlush
{
get
{
RequireDirectionIn();
return _device.InternalDevice.GetPipePolicyBool(_interfaceIndex, _pipeID, API.POLICY_TYPE.AUTO_FLUSH);
}
set
{
RequireDirectionIn();
_device.InternalDevice.SetPipePolicy(_interfaceIndex, _pipeID, API.POLICY_TYPE.AUTO_FLUSH, value);
}
}
/// <summary>
/// When true, read operations are completed only when the number of bytes requested by the client has been received. Default value is false.
/// Only available on IN direction pipes.
/// </summary>
/// <seealso href="http://msdn.microsoft.com/en-us/library/aa476439.aspx">WinUSB_GetPipePolicy for a more detailed description</seealso>
public bool IgnoreShortPackets
{
get
{
RequireDirectionIn();
return _device.InternalDevice.GetPipePolicyBool(_interfaceIndex, _pipeID, API.POLICY_TYPE.IGNORE_SHORT_PACKETS);
}
set
{
RequireDirectionIn();
_device.InternalDevice.SetPipePolicy(_interfaceIndex, _pipeID, API.POLICY_TYPE.IGNORE_SHORT_PACKETS, value);
}
}
/// <summary>
/// Specifies the timeout in milliseconds for pipe operations. If an operation does not finish within the specified time it will fail.
/// When set to zero, no timeout is used. Default value is zero.
/// </summary>
/// <seealso href="http://msdn.microsoft.com/en-us/library/aa476439.aspx">WinUSB_GetPipePolicy for a more detailed description</seealso>
public int PipeTransferTimeout
{
get
{
return (int)_device.InternalDevice.GetPipePolicyUInt(_interfaceIndex, _pipeID, API.POLICY_TYPE.PIPE_TRANSFER_TIMEOUT);
}
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException("Pipe transfer timeout cannot be negative.");
}
_device.InternalDevice.SetPipePolicy(_interfaceIndex, _pipeID, API.POLICY_TYPE.PIPE_TRANSFER_TIMEOUT, (uint)value);
}
}
/// <summary>
/// When true, read and write operations to the pipe must have a buffer length that is a multiple of the maximum endpoint packet size,
/// and the length must be less than the maximum transfer size. With these conditions met, data is sent directly to the USB driver stack,
/// bypassing the queuing and error handling of WinUSB.
/// Default value is false.
/// </summary>
/// <seealso href="http://msdn.microsoft.com/en-us/library/aa476439.aspx">WinUSB_GetPipePolicy for a more detailed description</seealso>
public bool RawIO
{
get
{
return _device.InternalDevice.GetPipePolicyBool(_interfaceIndex, _pipeID, API.POLICY_TYPE.RAW_IO);
}
set
{
_device.InternalDevice.SetPipePolicy(_interfaceIndex, _pipeID, API.POLICY_TYPE.RAW_IO, value);
}
}
/// <summary>
/// When true, every write request that is a multiple of the maximum packet size for the endpoint is terminated with a zero-length packet.
/// Default value is false. Only available on OUT direction pipes.
/// </summary>
/// <seealso href="http://msdn.microsoft.com/en-us/library/aa476439.aspx">WinUSB_GetPipePolicy for a more detailed description</seealso>
public bool ShortPacketTerminate
{
get
{
RequireDirectionOut();
return _device.InternalDevice.GetPipePolicyBool(_interfaceIndex, _pipeID, API.POLICY_TYPE.SHORT_PACKET_TERMINATE);
}
set
{
RequireDirectionOut();
_device.InternalDevice.SetPipePolicy(_interfaceIndex, _pipeID, API.POLICY_TYPE.SHORT_PACKET_TERMINATE, value);
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>