我可以在 UWP (HidD_GetPreparsedData) 中使用 Windows 隐藏 API 调用吗?
Can I Use Windows Hid API Calls in UWP (HidD_GetPreparsedData)?
我正在尝试在 UWP 上访问连接到我的计算机的 Hid (USB) 设备。我可以毫无问题地枚举设备并通过 .NET Core 中的 Windows API 调用与它们通信。在 UWP 中,我可以枚举设备,但是当我使用同一设备 (https://msdn.microsoft.com/en-us/library/windows/hardware/ff539679(v=vs.85).aspx) 调用 HidD_GetPreparsedData 时,它 returns false.
我在想,因为 UWP 有自己的 HID 库,我应该使用它,但我希望重用我现有的代码。知道为什么这个调用可能会失败吗?
我确实认为这是一个权限问题,所以我从这里下载了 UWP HID 示例:https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/CustomHidDeviceAccess。然后我修改了包清单以使用设备的 VID 和 PID
<Capabilities>
<DeviceCapability Name="humaninterfacedevice">
<Device Id="vidpid:xxxx xxxx">
<Function Type="usage:0005 *" />
</Device>
</DeviceCapability>
</Capabilities>
设备使用 UWP 中的标准 HID 库出现在 UWP 中。我可以枚举所有设备(不仅仅是我指定访问权限的设备),并且我的设备显示在示例应用程序的设备列表中。
然而,当我编译 运行 我的应用程序时,HidD_GetPreparsedData returns 错误。所以,我想知道我是否能让这个 API 调用正常工作。 IE。我应该放弃企业版而只使用标准的 UWP HID 库吗?
答案是否定的。 UWP 平台不允许 Windows Hid 或 USB API 调用。 API 调用没有出现 here。即使 API 没有明确失败。这就引出了一个问题,即为什么 UWP 允许您访问某些 API 调用,但在您调用它们时没有明确抛出异常。
无论如何,没有必要,因为USB 和Hid 访问是完整的API。您需要像这样在包清单中定义您的设备:
隐藏:
<DeviceCapability Name="humaninterfacedevice">
<Device Id="vidpid:534C 0001">
<Function Type="usage:0005 *" />
<Function Type="usage:FF00 0001" />
<Function Type="usage:ff00 *" />
</Device>
<Device Id="vidpid:1209 53C0">
<Function Type="usage:0005 *" />
<Function Type="usage:FF00 0001" />
<Function Type="usage:ff00 *" />
</Device>
<Device Id="vidpid:1209 53C1">
<Function Type="usage:0005 *" />
<Function Type="usage:FF00 0001" />
<Function Type="usage:ff00 *" />
</Device>
</DeviceCapability>
USB:
<!--Trezor Firmware 1.7.x -->
<Device Id="vidpid:1209 53C1">
<Function Type="classId:ff * *" />
</Device>
</DeviceCapability>
这里有两个 类 用于与 USB 通信,以及隐藏来自 Device.Net 的 UWP 设备。
public class UWPHidDevice : UWPDeviceBase<HidDevice>
{
#region Public Properties
public bool DataHasExtraByte { get; set; } = true;
#endregion
#region Public Override Properties
/// <summary>
/// TODO: These vales are completely wrong and not being used anyway...
/// </summary>
public override ushort WriteBufferSize => 64;
/// <summary>
/// TODO: These vales are completely wrong and not being used anyway...
/// </summary>
public override ushort ReadBufferSize => 64;
#endregion
#region Event Handlers
private void _HidDevice_InputReportReceived(HidDevice sender, HidInputReportReceivedEventArgs args)
{
HandleDataReceived(InputReportToBytes(args));
}
#endregion
#region Constructors
public UWPHidDevice()
{
}
public UWPHidDevice(string deviceId) : base(deviceId)
{
}
#endregion
#region Private Methods
private byte[] InputReportToBytes(HidInputReportReceivedEventArgs args)
{
byte[] bytes;
using (var stream = args.Report.Data.AsStream())
{
bytes = new byte[args.Report.Data.Length];
stream.Read(bytes, 0, (int)args.Report.Data.Length);
}
if (DataHasExtraByte)
{
bytes = RemoveFirstByte(bytes);
}
return bytes;
}
public override async Task InitializeAsync()
{
//TODO: Put a lock here to stop reentrancy of multiple calls
//TODO: Dispose but this seems to cause initialization to never occur
//Dispose();
Logger.Log("Initializing Hid device", null, nameof(UWPHidDevice));
await GetDevice(DeviceId);
if (_ConnectedDevice != null)
{
_ConnectedDevice.InputReportReceived += _HidDevice_InputReportReceived;
RaiseConnected();
}
else
{
throw new Exception($"The device {DeviceId} failed to initialize");
}
}
protected override IAsyncOperation<HidDevice> FromIdAsync(string id)
{
return HidDevice.FromIdAsync(id, FileAccessMode.ReadWrite);
}
#endregion
#region Public Methods
public override async Task WriteAsync(byte[] data)
{
byte[] bytes;
if (DataHasExtraByte)
{
bytes = new byte[data.Length + 1];
Array.Copy(data, 0, bytes, 1, data.Length);
bytes[0] = 0;
}
else
{
bytes = data;
}
var buffer = bytes.AsBuffer();
var outReport = _ConnectedDevice.CreateOutputReport();
outReport.Data = buffer;
try
{
var operation = _ConnectedDevice.SendOutputReportAsync(outReport);
await operation.AsTask();
Tracer?.Trace(false, bytes);
}
catch (ArgumentException ex)
{
//TODO: Check the string is nasty. Validation on the size of the array being sent should be done earlier anyway
if (ex.Message == "Value does not fall within the expected range.")
{
throw new Exception("It seems that the data being sent to the device does not match the accepted size. Have you checked DataHasExtraByte?", ex);
}
throw;
}
}
#endregion
}
public class UWPUsbDevice : UWPDeviceBase<UsbDevice>
{
#region Fields
/// <summary>
/// TODO: It should be possible to select a different configuration/interface
/// </summary>
private UsbInterface _DefaultConfigurationInterface;
private UsbInterruptOutPipe _DefaultOutPipe;
private UsbInterruptInPipe _DefaultInPipe;
#endregion
#region Public Override Properties
public override ushort WriteBufferSize => (ushort)_DefaultOutPipe.EndpointDescriptor.MaxPacketSize;
public override ushort ReadBufferSize => (ushort)_DefaultInPipe.EndpointDescriptor.MaxPacketSize;
#endregion
#region Constructors
public UWPUsbDevice() : base()
{
}
public UWPUsbDevice(string deviceId) : base(deviceId)
{
}
#endregion
#region Private Methods
public override async Task InitializeAsync()
{
await GetDevice(DeviceId);
if (_ConnectedDevice != null)
{
var usbInterface = _ConnectedDevice.Configuration.UsbInterfaces.FirstOrDefault();
if (usbInterface == null)
{
_ConnectedDevice.Dispose();
throw new Exception("There was no Usb Interface found for the device.");
}
var interruptPipe = usbInterface.InterruptInPipes.FirstOrDefault();
if (interruptPipe == null)
{
throw new Exception("There was no interrupt pipe found on the interface");
}
interruptPipe.DataReceived += InterruptPipe_DataReceived;
//TODO: Fill in the DeviceDefinition...
// TODO: It should be possible to select a different configurations, interface, and pipes
_DefaultConfigurationInterface = _ConnectedDevice.Configuration.UsbInterfaces.FirstOrDefault();
//TODO: Clean up this messaging and move down to a base class across platforms
if (_DefaultConfigurationInterface == null) throw new Exception("Could not get the default interface configuration for the USB device");
_DefaultOutPipe = _DefaultConfigurationInterface.InterruptOutPipes.FirstOrDefault();
if (_DefaultOutPipe == null) throw new Exception("Could not get the default out pipe for the default USB interface");
_DefaultInPipe = _DefaultConfigurationInterface.InterruptInPipes.FirstOrDefault();
if (_DefaultOutPipe == null) throw new Exception("Could not get the default in pipe for the default USB interface");
RaiseConnected();
}
else
{
throw new Exception($"Could not connect to device with Device Id {DeviceId}. Check that the package manifest has been configured to allow this device.");
}
}
protected override IAsyncOperation<UsbDevice> FromIdAsync(string id)
{
return UsbDevice.FromIdAsync(id);
}
#endregion
#region Event Handlers
private void InterruptPipe_DataReceived(UsbInterruptInPipe sender, UsbInterruptInEventArgs args)
{
HandleDataReceived(args.InterruptData.ToArray());
}
#endregion
#region Public Methods
public override async Task WriteAsync(byte[] bytes)
{
if (_DefaultOutPipe == null) throw new Exception("The device has not been initialized.");
if (bytes.Length > WriteBufferSize) throw new Exception("The buffer size is too large");
await _DefaultOutPipe.OutputStream.WriteAsync(bytes.AsBuffer());
Tracer?.Trace(false, bytes);
}
#endregion
}
我正在尝试在 UWP 上访问连接到我的计算机的 Hid (USB) 设备。我可以毫无问题地枚举设备并通过 .NET Core 中的 Windows API 调用与它们通信。在 UWP 中,我可以枚举设备,但是当我使用同一设备 (https://msdn.microsoft.com/en-us/library/windows/hardware/ff539679(v=vs.85).aspx) 调用 HidD_GetPreparsedData 时,它 returns false.
我在想,因为 UWP 有自己的 HID 库,我应该使用它,但我希望重用我现有的代码。知道为什么这个调用可能会失败吗?
我确实认为这是一个权限问题,所以我从这里下载了 UWP HID 示例:https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/CustomHidDeviceAccess。然后我修改了包清单以使用设备的 VID 和 PID
<Capabilities>
<DeviceCapability Name="humaninterfacedevice">
<Device Id="vidpid:xxxx xxxx">
<Function Type="usage:0005 *" />
</Device>
</DeviceCapability>
</Capabilities>
设备使用 UWP 中的标准 HID 库出现在 UWP 中。我可以枚举所有设备(不仅仅是我指定访问权限的设备),并且我的设备显示在示例应用程序的设备列表中。
然而,当我编译 运行 我的应用程序时,HidD_GetPreparsedData returns 错误。所以,我想知道我是否能让这个 API 调用正常工作。 IE。我应该放弃企业版而只使用标准的 UWP HID 库吗?
答案是否定的。 UWP 平台不允许 Windows Hid 或 USB API 调用。 API 调用没有出现 here。即使 API 没有明确失败。这就引出了一个问题,即为什么 UWP 允许您访问某些 API 调用,但在您调用它们时没有明确抛出异常。
无论如何,没有必要,因为USB 和Hid 访问是完整的API。您需要像这样在包清单中定义您的设备:
隐藏:
<DeviceCapability Name="humaninterfacedevice">
<Device Id="vidpid:534C 0001">
<Function Type="usage:0005 *" />
<Function Type="usage:FF00 0001" />
<Function Type="usage:ff00 *" />
</Device>
<Device Id="vidpid:1209 53C0">
<Function Type="usage:0005 *" />
<Function Type="usage:FF00 0001" />
<Function Type="usage:ff00 *" />
</Device>
<Device Id="vidpid:1209 53C1">
<Function Type="usage:0005 *" />
<Function Type="usage:FF00 0001" />
<Function Type="usage:ff00 *" />
</Device>
</DeviceCapability>
USB:
<!--Trezor Firmware 1.7.x -->
<Device Id="vidpid:1209 53C1">
<Function Type="classId:ff * *" />
</Device>
</DeviceCapability>
这里有两个 类 用于与 USB 通信,以及隐藏来自 Device.Net 的 UWP 设备。
public class UWPHidDevice : UWPDeviceBase<HidDevice>
{
#region Public Properties
public bool DataHasExtraByte { get; set; } = true;
#endregion
#region Public Override Properties
/// <summary>
/// TODO: These vales are completely wrong and not being used anyway...
/// </summary>
public override ushort WriteBufferSize => 64;
/// <summary>
/// TODO: These vales are completely wrong and not being used anyway...
/// </summary>
public override ushort ReadBufferSize => 64;
#endregion
#region Event Handlers
private void _HidDevice_InputReportReceived(HidDevice sender, HidInputReportReceivedEventArgs args)
{
HandleDataReceived(InputReportToBytes(args));
}
#endregion
#region Constructors
public UWPHidDevice()
{
}
public UWPHidDevice(string deviceId) : base(deviceId)
{
}
#endregion
#region Private Methods
private byte[] InputReportToBytes(HidInputReportReceivedEventArgs args)
{
byte[] bytes;
using (var stream = args.Report.Data.AsStream())
{
bytes = new byte[args.Report.Data.Length];
stream.Read(bytes, 0, (int)args.Report.Data.Length);
}
if (DataHasExtraByte)
{
bytes = RemoveFirstByte(bytes);
}
return bytes;
}
public override async Task InitializeAsync()
{
//TODO: Put a lock here to stop reentrancy of multiple calls
//TODO: Dispose but this seems to cause initialization to never occur
//Dispose();
Logger.Log("Initializing Hid device", null, nameof(UWPHidDevice));
await GetDevice(DeviceId);
if (_ConnectedDevice != null)
{
_ConnectedDevice.InputReportReceived += _HidDevice_InputReportReceived;
RaiseConnected();
}
else
{
throw new Exception($"The device {DeviceId} failed to initialize");
}
}
protected override IAsyncOperation<HidDevice> FromIdAsync(string id)
{
return HidDevice.FromIdAsync(id, FileAccessMode.ReadWrite);
}
#endregion
#region Public Methods
public override async Task WriteAsync(byte[] data)
{
byte[] bytes;
if (DataHasExtraByte)
{
bytes = new byte[data.Length + 1];
Array.Copy(data, 0, bytes, 1, data.Length);
bytes[0] = 0;
}
else
{
bytes = data;
}
var buffer = bytes.AsBuffer();
var outReport = _ConnectedDevice.CreateOutputReport();
outReport.Data = buffer;
try
{
var operation = _ConnectedDevice.SendOutputReportAsync(outReport);
await operation.AsTask();
Tracer?.Trace(false, bytes);
}
catch (ArgumentException ex)
{
//TODO: Check the string is nasty. Validation on the size of the array being sent should be done earlier anyway
if (ex.Message == "Value does not fall within the expected range.")
{
throw new Exception("It seems that the data being sent to the device does not match the accepted size. Have you checked DataHasExtraByte?", ex);
}
throw;
}
}
#endregion
}
public class UWPUsbDevice : UWPDeviceBase<UsbDevice>
{
#region Fields
/// <summary>
/// TODO: It should be possible to select a different configuration/interface
/// </summary>
private UsbInterface _DefaultConfigurationInterface;
private UsbInterruptOutPipe _DefaultOutPipe;
private UsbInterruptInPipe _DefaultInPipe;
#endregion
#region Public Override Properties
public override ushort WriteBufferSize => (ushort)_DefaultOutPipe.EndpointDescriptor.MaxPacketSize;
public override ushort ReadBufferSize => (ushort)_DefaultInPipe.EndpointDescriptor.MaxPacketSize;
#endregion
#region Constructors
public UWPUsbDevice() : base()
{
}
public UWPUsbDevice(string deviceId) : base(deviceId)
{
}
#endregion
#region Private Methods
public override async Task InitializeAsync()
{
await GetDevice(DeviceId);
if (_ConnectedDevice != null)
{
var usbInterface = _ConnectedDevice.Configuration.UsbInterfaces.FirstOrDefault();
if (usbInterface == null)
{
_ConnectedDevice.Dispose();
throw new Exception("There was no Usb Interface found for the device.");
}
var interruptPipe = usbInterface.InterruptInPipes.FirstOrDefault();
if (interruptPipe == null)
{
throw new Exception("There was no interrupt pipe found on the interface");
}
interruptPipe.DataReceived += InterruptPipe_DataReceived;
//TODO: Fill in the DeviceDefinition...
// TODO: It should be possible to select a different configurations, interface, and pipes
_DefaultConfigurationInterface = _ConnectedDevice.Configuration.UsbInterfaces.FirstOrDefault();
//TODO: Clean up this messaging and move down to a base class across platforms
if (_DefaultConfigurationInterface == null) throw new Exception("Could not get the default interface configuration for the USB device");
_DefaultOutPipe = _DefaultConfigurationInterface.InterruptOutPipes.FirstOrDefault();
if (_DefaultOutPipe == null) throw new Exception("Could not get the default out pipe for the default USB interface");
_DefaultInPipe = _DefaultConfigurationInterface.InterruptInPipes.FirstOrDefault();
if (_DefaultOutPipe == null) throw new Exception("Could not get the default in pipe for the default USB interface");
RaiseConnected();
}
else
{
throw new Exception($"Could not connect to device with Device Id {DeviceId}. Check that the package manifest has been configured to allow this device.");
}
}
protected override IAsyncOperation<UsbDevice> FromIdAsync(string id)
{
return UsbDevice.FromIdAsync(id);
}
#endregion
#region Event Handlers
private void InterruptPipe_DataReceived(UsbInterruptInPipe sender, UsbInterruptInEventArgs args)
{
HandleDataReceived(args.InterruptData.ToArray());
}
#endregion
#region Public Methods
public override async Task WriteAsync(byte[] bytes)
{
if (_DefaultOutPipe == null) throw new Exception("The device has not been initialized.");
if (bytes.Length > WriteBufferSize) throw new Exception("The buffer size is too large");
await _DefaultOutPipe.OutputStream.WriteAsync(bytes.AsBuffer());
Tracer?.Trace(false, bytes);
}
#endregion
}