マイクロコントローラーを USB でプログラムし、WinUSB を使用して PC に接続しました。同期 (ブロッキング、オーバーラップ = NULL) 呼び出しで pinvoke を使用して、c++ および VB.NET で uC と通信できます。C ++で非同期呼び出しが機能するようになりましたが、.NETでコーディングする方法がわかりません。よくわからない別の質問もあります。
質問 1: C++ で、最初に IO Completion Callback オブジェクトを使用して非同期呼び出しを試みましたが、読み取りのために WinUSB を呼び出すと、ファイル ハンドル (HANDLE hDevice) で非同期 IO が既に進行中であるというエラーが返され、CreateThreadpoolIo は WinUSB ハンドルを取得しませんでした ( PWINUSB_INTERFACE_HANDLE hWinUsbHandle)。私の推測では、WinUSB はこのファイル ハンドルを IO に使用するため、使用できません。最終的には、Wait Callback オブジェクトを使用して動作するようになりました。現在は機能しています。私がしているのは正しい使用法であることを明確にしたいだけです。
私が少し混乱したのは、CreateThreadpoolWait 呼び出しです。最初は、これは MSDN の他の例のようにスレッド プールを作成していると思っていましたが、今では、既定のスレッド プールで実行される単なるオブジェクトだと思います。また、objConext は、呼び出しをコールバックと同期するために使用する変数です。つまり、コールバックの PVOID コンテキストは、objConext を指します。
以下は C コードのスニペットです。
// Receive data asynchronously
void BeginReceiveData(UCHAR bytPipeId, UCHAR *bytData, ULONG intLength, PTP_WAIT_CALLBACK cbCallback)
{
// Create the event
HANDLE hEventCallback = CreateEvent(NULL, FALSE, FALSE, NULL);
// Check for an error
if (!hEventCallback)
{
// Set the error
printf(strError, "Error creating callback event: %d.", GetLastError());
}
// Create the thread pool wait object
tpWait = CreateThreadpoolWait(cbCallback, &objConext, NULL);
// Check for an error
if (!tpWait)
{
// Set the error
printf(strError, "Error creating thread pool: %d.", GetLastError());
}
// Place the wait object in the thread pool
SetThreadpoolWait(tpWait, hEventCallback, NULL);
// Clear the callback
ZeroMemory(&oCallback, sizeof(oCallback));
// Set the event
oCallback.hEvent = hEventCallback;
// Read
BOOL bResult = WinUsb_ReadPipe(*hWinUsbHandle, bytPipeId, bytData, intLength, NULL, &oCallback);
// Check for an error
if (!bResult)
{
if (GetLastError() != ERROR_IO_PENDING)
{
// Set the error
printf(strError, "Error reading pipe: %d.", GetLastError());
}
}
}
// Receive data callback
void CALLBACK UsbCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult)
{
}
質問 2: 上記を .NET に変換するにはどうすればよいですか? これまでの私の推測は次のとおりです。
Dim wait As New WaitCallback(AddressOf UsbCallback)
' Create the event
Dim eEventCallback As New AutoResetEvent(False)
ThreadPool.QueueUserWorkItem(wait, Nothing)
Dim oCallback As New Overlapped(0, 0, eEventCallback.SafeWaitHandle.DangerousGetHandle, Nothing)
'Dim oCallback As New NativeOverlapped
oCallback.EventHandleIntPtr = eEventCallback.SafeWaitHandle.DangerousGetHandle
' Read
Dim bResult As Boolean = WinUsb_ReadPipe(hWinUsbHandle, bytPipeId, bytData, intLength, intLength, oCallback.EventHandleIntPtr)
<DllImport("winusb.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Public Function WinUsb_ReadPipe(InterfaceHandle As IntPtr, PipeID As Byte, Buffer() As Byte, BufferLength As UInteger, ByRef LengthTransferred As UInteger, Overlapped As IntPtr) As Boolean
End Function
編集:回答として投稿するつもりでしたが、ストリートの信用が十分ではありません
まあ、私はコードを .NET に変換する答えを持っています。すべての API 関数を PInvoke に変換し、いくつかを除いて .NET に組み込まれた関数を置き換えました。コードは機能しますが、コールバックが行われる前に GC がそれらをクリーンアップするため、変数が宣言されている場所に注意する必要があります。また、コンテキストは慎重に割り当てて破棄する必要があります。たとえば、コンテキスト構造内の配列は、適切なサイズのメモリが割り当てられていない場合に消去される可能性があります。これは理解しやすいものです。
これらの理由から、USB のすべてのパイプのオブジェクトにハンドル参照を保持し、それらを再利用する方が有益な場合があります。これは、前のパイプが完了する前に同じパイプに対して複数の呼び出しを行うことができないためです。これが行われると、コンテキストで同じことを行うことができ、コンテキストへのメモリの割り当てと破棄を回避できます。たとえば、辞書はコールバックの Wait ポインターで使用できます。
Dim dicHandlesAndContext As Dictionary(Of IntPtr, HandlesAndContext)
質問 1 に対する答えはまだ見つかっていませんが、これが「正しい」方法であるかどうかはわかりませんが、非常にうまく機能しているようです。主なコード スニペットは次のとおりです。
' Receive data asynchronously
Private Sub BeginReceiveData(bytPipeId As Byte, bytData() As Byte, intLength As UInteger)
' Allocate memory for the callback
ptrContext = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(TestContext)))
' Copy the structure to memory
Marshal.StructureToPtr(objConext, ptrContext, True)
' Create the callback
cbCallback = New UsbCallbackDelegate(AddressOf UsbCallback)
' Create the event
Dim areEvent As New AutoResetEvent(False)
' Set the event
hEventCallback = areEvent.SafeWaitHandle.DangerousGetHandle
' Create the thread pool wait object
tpWait = CreateThreadpoolWait(Marshal.GetFunctionPointerForDelegate(cbCallback), ptrContext, IntPtr.Zero)
' Place the wait object in the thread pool
SetThreadpoolWait(tpWait, hEventCallback, IntPtr.Zero)
' Set the event
oCallback.EventHandle = hEventCallback
' Read
Dim bResult As Boolean = WinUsb_ReadPipe(hWinUsbHandle, bytPipeId, bytData, intLength, intLength, oCallback)
If Not bResult Then
If Not Marshal.GetLastWin32Error = 997 Then
' Get the error
Dim intError As Integer = Marshal.GetLastWin32Error
Throw New Win32Exception(intError, "Error getting device information.")
End If
End If
End Sub
' Delegate for USB callbacks
Private Delegate Sub UsbCallbackDelegate(Instance As IntPtr, Context As IntPtr, Wait As IntPtr, WaitResult As UInteger)
' Callback
Private Sub UsbCallback(Instance As IntPtr, Context As IntPtr, Wait As IntPtr, WaitResult As UInteger)
' Number of bytes transferred
Dim intTransferred As UInteger
' Get the number of bytes transferred
WinUsb_GetOverlappedResult(hWinUsbHandle, oCallback, intTransferred, False)
' Get the context from memory
Dim objConext As TestContext = Marshal.PtrToStructure(Context, GetType(TestContext))
' Free the memory
Marshal.FreeHGlobal(Context)
' Do some work on the data
End Sub
メイン API DLL インポート
<DllImport("winusb.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Public Function WinUsb_GetOverlappedResult(InterfaceHandle As IntPtr, ByRef lpOverlapped As Threading.NativeOverlapped, ByRef lpNumberOfBytesTransferred As UInteger, bWait As Boolean) As IntPtr
End Function
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Public Function CreateThreadpoolWait(pfnwa As IntPtr, pv As IntPtr, pcbe As IntPtr) As IntPtr
End Function
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Public Function SetThreadpoolWait(pwa As IntPtr, h As IntPtr, pftTimeout As IntPtr) As IntPtr
End Function
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Public Function CloseThreadpoolWait(pwa As IntPtr) As IntPtr
End Function
データを継続的に受信し、同時に GC を実行することで、これらの関数の GC 保護をテストしました。何かが収集されると、プログラムはクラッシュします。
' Receive data asynchronously on endpoint 1
BeginReceiveData(129, bytData, 8)
While True
' Garbage collection
GC.Collect()
GC.WaitForFullGCComplete()
End While
' Callback
Private Sub UsbCallback(Instance As IntPtr, Context As IntPtr, Wait As IntPtr, WaitResult As UInteger)
' ...
' Once data is received, receive more
BeginReceiveData(129, bytData, 8)
'...
End Sub
警告: 多くのクリーンアップ コードはここにはありません。これを適切に行う方法については、MSDN で C コードを確認してください。オブジェクト指向のアプローチは、ハンドルを有効に保ち、IDIsposable を実装してクリーンアップするのに最適だと思います。
オープン ソース DLL に興味がある人はいますか? :P