2012-01-25 25 views
1

私の目標は、USB HIDデバイスと通信できるアプリケーションを作成することです。メイン(UI)スレッドは、適切なハンドルを作成します。P/Invoke(Win32 API)と並行スレッドとのUSB HID通信

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

この後、2つのスレッドが開始されます。 1つ目は、デバイスからのデータの読み取りを継続することです。利用可能なデータがない場合、ブロックされます。重複した読みを使用します。ハンドルは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"); 
} 

アプリケーションは、デバイスによって正しくプッシュされたデータを受信します。プログラムは何の失敗もなく円滑に実行されます。 BUTアプリケーションが(コマンドオブジェクトを前述のキューに配置することによって)デバイスにデータを送信する場合、アプリケーション全体がクラッシュする自発的に。クラッシュは不確定的なやり方で起こる。これの背後にある理由は何ですか?私の考えは、それが同時アクセスによって引き起こされるということです。しかし、(ハンドルを渡すことによって)FileStreamオブジェクトを同時に使用しても、このようなクラッシュは発生しません。

+0

何も、クラッシュの詳細が重要です。この種のコードは書かないでください.FileStream(SafeFileHandle、FileAccess、int、bool)コンストラクタを使用すれば、CreateFileをピンボケするだけで、.NET以外の方法でも実行できます。 –

+0

@HansPassant Unfortunatelly例外なくアプリケーションがクラッシュするため、クラッシュの詳細を伝えることはできません。 私は** ** P/Invokeを実装する必要があると述べました。誰かがSilverlight 5のFileStreamオブジェクトにハンドルをプッシュする回避策がない限り。 – tamasf

答えて

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(); 
     } 
    } 

}