2

我用 USB 编程了一个微控制器,并使用 WinUSB 将它连接到 PC。我能够在 c++ 和 VB.NET 中使用 pinvoke 与同步(阻塞,重叠 = NULL)调用与我的 uC 通信。我刚刚在 c++ 中进行了异步调用,但我不知道如何在 .NET 中对其进行编码,还有另一个我不确定的问题:

问题 1:在 c++ 中,我最初尝试使用 IO Completion Callback Objects 进行异步调用,但是当我调用 WinUSB 读取它时返回一个错误,即异步 IO 已在文件句柄(HANDLE hDevice)上进行,CreateThreadpoolIo 不会使用 WinUSB 句柄( PWINUSB_INTERFACE_HANDLE hWinUsbHandle)。我的猜测是 WinUSB 将此文件句柄用于 IO,所以我不能。我最终使用等待回调对象让它工作。它现在正在工作,我只想澄清我所做的是正确的用法。

我有点困惑的部分是 CreateThreadpoolWait 调用。起初我认为这是像 MSDN 中的其他示例一样创建一个线程池,但是我认为现在它只是一个在默认线程池上运行的对象。此外,objConext 是用于将调用与其回调同步的变量,即回调中的 PVOID Context 指向 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 中每个管道的对象中并重用它们可能更有益,因为在前一个管道完成之前无法对同一个管道进行多次调用。如果这样做了,那么您可以对上下文执行相同的操作,并避免为上下文分配和销毁内存。例如,可以在回调中的等待指针上使用字典:

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

4

0 回答 0