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