我可以在 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
}