【问题标题】:USB HID communication with P/Invoke (Win32 API) and concurrent threads与 P/Invoke (Win32 API) 和并发线程的 USB HID 通信
【发布时间】:2012-02-19 01:08:14
【问题描述】:

我的目标是创建一个能够与 USB HID 设备通信的应用程序。主 (UI) 线程创建适当的句柄。

 this.handle =
        Win32.CreateFile(
            devicePath,
            Win32.GENERIC_READ | Win32.GENERIC_WRITE,
            0,
            IntPtr.Zero,
            Win32.OPEN_EXISTING,
            Win32.FILE_FLAG_OVERLAPPED,
            IntPtr.Zero);

在此之后,启动两个线程。首先是不断地从设备中读取数据。如果没有可用数据,则将其阻止。它使用重叠阅读。句柄可以传递给 FileStream 对象,但我必须使用 P/Invoke 调用。我的实现基于多个 HID 库。于是线程运行如下循环:

while (true)
{
    var overlapped = new Win32.Overlapped();
    overlapped.Offset = 0;
    overlapped.OffsetHigh = 0;
    overlapped.Event = this.readEvent;

    var readResult = 
        Win32.ReadFile(
            this.handle,
            buffer, 
            (uint)this.InputReportLength, 
            IntPtr.Zero, 
            ref overlapped);

    if (readResult == 0)
    {
        int errorCode = Marshal.GetLastWin32Error();

        // Overlapped operation is running => 0x3e5
        if (errorCode != 0x3e5)
        {
            break;
        }
    }

    var result = Win32.WaitForSingleObject(overlapped.Event, Win32.WAIT_INFINITE);

    if (result != Win32.WAIT_OBJECT_0 || this.handle == IntPtr.Zero)
    {
        // Handle is cleared
        break;
    }

    uint bytesRead = 0;
    Win32.GetOverlappedResult(this.handle, ref overlapped, out bytesRead, false);

    if (bytesRead > 0)
    {
        byte[] report = new byte[this.InputReportLength];
        Array.Copy(buffer, report, Math.Min(bytesRead, report.Length));

        // Report data
        OnDataReceived(report);
    }
}

第二个线程使用并发队列中的命令,将它们编组为二进制,然后调用以下方法将数据写入设备。

 IntPtr writeEvent = Win32.CreateEvent(IntPtr.Zero, false, true, Guid.NewGuid().ToString());

 var overlapped = new Win32.Overlapped();
 overlapped.Offset = 0;
 overlapped.OffsetHigh = 0;
 overlapped.Event = writeEvent;

 int writeResult = 
    Win32.WriteFile(
       this.handle, buffer, 
       (uint)buffer.Length,
       IntPtr.Zero, 
       ref overlapped);

 if (writeResult == 0)
 {
     int errorCode = Marshal.GetLastWin32Error();

     // Overlapped operation is running => 0x3e5
     if (errorCode != 0x3e5)
     {
         throw new IOException(string.Format("Cannot write device ({0})", errorCode));
     }
 }

 var result = Win32.WaitForSingleObject(writeEvent, Win32.WAIT_INFINITE);

 Win32.CloseHandle(writeEvent);

 if (result != Win32.WAIT_OBJECT_0)
 {
     throw new IOException("Failed to write");
 }

应用程序正确接收设备推送的数据。该程序运行顺利,没有任何故障。 但是如果应用程序向设备发送数据(通过将命令对象放入提到的队列中),整个应用程序会自发崩溃。崩溃以不确定的方式发生。这背后的原因是什么?我的想法是它是由并发访问引起的。但是,同时使用 FileStream 对象(通过将句柄传递给它)不会导致此类崩溃。

【问题讨论】:

  • 没什么可看的,崩溃细节很重要。只是不要编写这种代码,使用 FileStream(SafeFileHandle, FileAccess, int, bool) 构造函数,这样您只需要 pinvoke CreateFile 并且可以以 .NET 方式执行其他所有操作。
  • @HansPassant 不幸的是,我无法向您提供崩溃的详细信息,因为应用程序无一例外地崩溃。我提到我必须使用 P/Invoke 实现。除非有人有办法将句柄推入 Silverlight 5 的 FileStream 对象。

标签: .net winapi usb pinvoke


【解决方案1】:

我终于意识到问题所在了。在 P/Invoke 调用和 IO 操作完成之间,CLR 可以重新组织内存布局。二进制缓冲区或Overlapped 结构可能会移动到新的内存区域。 IO 完成后,结果数据被写入原始内存区域,导致应用程序崩溃。

解决方案是在 IO 操作挂起时固定内存区域。可以使用fixed 关键字来固定字节数组。 Overlapped 结构体不能这样固定,可以使用 GCHandle.Alloc 方法。

以下代码演示了修改后的写操作:

public unsafe void Send(byte[] data)
{
    byte[] buffer = new byte[this.OutputReportLength];
    Array.Copy(data, 1, buffer, 1, Math.Min(buffer.Length, data.Length) - 1);

    fixed (byte* bufferPointer = buffer)
    {
        IntPtr writeEvent = Win32.CreateEvent(IntPtr.Zero, false, true, Guid.NewGuid().ToString());

        var overlapped = new Win32.Overlapped();
        overlapped.Offset = 0;
        overlapped.OffsetHigh = 0;
        overlapped.Event = writeEvent;

        GCHandle pinnedOverlapped = GCHandle.Alloc(overlapped, GCHandleType.Pinned);

        try
        {
            int writeResult =
                Win32.WriteFile(
                    this.handle,
                    bufferPointer,
                    (uint)this.OutputReportLength,
                    IntPtr.Zero,
                    &overlapped);

            if (writeResult == 0)
            {
                int errorCode = Marshal.GetLastWin32Error();

                // Overlapped operation is running => 0x3e5 
                if (errorCode != 0x3e5)
                {
                    throw new IOException(string.Format("Cannot write device ({0})", errorCode));
                }
            }

            var result = Win32.WaitForSingleObject(writeEvent, Win32.WAIT_INFINITE);

            if (result != Win32.WAIT_OBJECT_0)
            {
                throw new IOException("Failed to write");
            }
        }
        finally
        {
            Win32.CloseHandle(writeEvent);
            pinnedOverlapped.Free();
        }
    }

}

【讨论】:

    猜你喜欢
    • 2023-04-07
    • 2021-01-24
    • 2014-04-18
    • 1970-01-01
    • 2013-08-10
    • 2013-05-31
    • 2012-07-26
    • 2011-02-23
    • 2021-07-28
    相关资源
    最近更新 更多