与P / Invoke(Win32 API)和并发线程的USB HID通信

时间:2012-01-25 20:19:06

标签: .net winapi usb pinvoke

我的目标是创建一个能够与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对象(通过将句柄传递给它)不会导致此类崩溃。

1 个答案:

答案 0 :(得分:2)

我终于意识到问题所在。在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();
        }
    }

}