從 Windows 8.1 開始,WinUSB Functions 集具備 API,可讓桌面應用程式在 USB 裝置的等時性端點來回傳輸資料。 針對這類應用程式,Microsoft提供的 Winusb.sys 必須是設備驅動器。
本文提供下列資訊:
- 等時性傳輸的簡要概述。
- 根據端點間隔值傳輸緩衝區計算。
- 使用 WinUSB Functions 傳送讀取和寫入同步數據的傳輸。
重要 API
從 Windows 8.1 開始,WinUSB Functions 集合具有 API,可讓桌面應用程式在 USB 裝置的等時性端點來回傳輸數據。 針對這類應用程式,Microsoft提供的 Winusb.sys 必須是設備驅動器。
USB 裝置可以支援同步端點,以穩定速率傳輸時間相依的數據,例如進行音訊或視訊串流。 沒有確保的送達。 良好的連線不應該丟失任何封包,遺失封包既不正常也不應被預期,但等時性協定對這類損失具有容忍度。
主機控制器會在總線上的保留期間傳送或接收數據,稱為 總線間隔。 總線間隔的單位取決於總線速度。 若為全速,則使用 1 毫秒的幀,對於高速和 SuperSpeed,則使用 250 微秒的微幀。
主機控制器會定期檢查此裝置。 針對讀取作業,當端點準備好傳送數據時,裝置會以總線間隔傳送數據來回應。 若要寫入裝置,主機控制器會傳送數據。
應用程式可以在一個服務間隔中傳送多少數據
本主題中的等時封包一詞是指在一個服務間隔中傳輸的數據量。 該值是由 USB 驅動程式堆疊計算,而應用程式可以在查詢管道屬性時取得該值。
無時針封包的大小會決定應用程式配置之傳輸緩衝區的大小。 緩衝區必須結束於框架界限。 傳輸的總大小取決於應用程式想要傳送或接收的數據量。 應用程式起始傳輸之後,主機會將傳輸緩衝區封包化,以便在每個間隔內傳送或接收每個間隔允許的最大位元組。
在資料傳輸過程中,並不是所有的匯流排間隔都會被使用。 在本主題中,所使用的公車間隔稱為服務間隔。
如何計算傳輸數據的框架
應用程式可以選擇使用下列兩種方式之一來指定框架:
- 自動。 在此模式中,應用程式會指示 USB 驅動程式堆疊在下一個適當的幀中傳送傳輸。 應用程式也必須指定緩衝區是否為連續串流,以便驅動程式堆疊能夠計算開始幀。
- 指定比當前幀更晚的起始幀。 應用程式應該考慮應用程式開始傳輸的時間和 USB 驅動程式堆疊處理它之間的延遲。
程式代碼範例討論
本主題中的範例示範如何使用這些 WinUSB 函式:
- WinUsb_QueryPipeEx
- WinUsb_RegisterIsochBuffer
- WinUsb_UnregisterIsochBuffer
- WinUsb_WriteIsochPipeAsap
- WinUsb_ReadIsochPipeAsap
- WinUsb_WriteIsochPipe
- WinUsb_ReadIsochPipe
- WinUsb_GetCurrentFrameNumber
- WinUsb_GetAdjustedFrameNumber
在本主題中,我們將以三種傳輸方式讀取和寫入 30 毫秒的數據到高速裝置。 管道能夠在每個服務間隔中傳輸 1024 個字節。 由於輪詢間隔為 1,因此數據會在框架的每個微框架中傳輸。 總共 30 個畫面格會攜帶 30*8*1024 個字節。
函式會呼叫傳送讀取和寫入傳輸的類似。 應用程式會配置足以容納這三個傳輸的傳輸緩衝區。 應用程式會藉由呼叫 WinUsb_RegisterIsochBuffer 來註冊特定管道的緩衝區。 呼叫會傳回用來傳送傳輸的註冊句柄。 緩衝區會針對後續傳輸重複使用,而緩衝區中的位移會調整為傳送或接收下一組數據。
範例中的所有傳輸都會以異步方式傳送。 為此,應用程式會配置一個包含三個元素的 OVERLAPPED 結構陣列,每個傳輸各一個元素。 應用程式會提供事件,以便在傳輸完成並擷取作業結果時收到通知。 為此,在陣列中的每個 重疊 結構中,應用程式會分配事件,並在 hEvent 成員中設置句柄。
此影像顯示使用 WinUsb_ReadIsochPipeAsap 函式 的三個讀取傳輸。 呼叫會指定每個傳輸的位移和長度。 ContinueStream 參數值為 FALSE,表示新的數據流。 之後,應用程式會要求後續傳輸會緊接在先前要求的最後一個畫面之後排程,以允許連續串流數據。 同步封包的數量會計算為每個幀的封包數乘以幀的數量,即 8*10。 針對此呼叫,應用程式不需要擔心計算開始畫面編號。
此影像顯示三個使用 WinUsb_WriteIsochPipe 函式的寫入資料傳輸。 呼叫會指定每個傳輸的位移和長度。 在此情況下,應用程式必須計算主機控制器可以開始傳送數據的框架編號。 在輸出時,函式會接收到上一個傳輸中所用的最後一個畫面格之後的下一個畫面格編號。 若要取得目前的框架,應用程式會 呼叫 WinUsb_GetCurrentFrameNumber。 此時,應用程式必須確定下一個傳輸的開始畫面格晚於目前畫面格,讓USB驅動程式堆疊不會卸除延遲封包。 若要這樣做,應用程式會呼叫 WinUsb_GetAdjustedFrameNumber 以取得實際的目前幀號碼(這比收到的目前幀號碼要晚)。 為了在安全方面,應用程式會再新增五個畫面格,然後傳送傳輸。
每次傳輸完成之後,應用程式會藉由呼叫 WinUsb_GetOverlappedResult 來取得傳輸的結果。 bWait 參數會設定為 TRUE,因此呼叫在作業完成之前不會傳回。 針對讀取和寫入傳輸, lpNumberOfBytesTransferred 參數一律為 0。 針對寫入傳輸,應用程式會假設如果作業順利完成,則會傳輸所有位元組。 針對讀取傳輸,每個異時態封包的 Length 成員(USBD_ISO_PACKET_DESCRIPTOR),包含每個間隔中傳輸的位元組數目。 若要取得總長度,應用程式會新增所有 Length 值。
完成時,應用程式會藉由呼叫 WinUsb_UnregisterIsochBuffer 釋放不連續的緩衝區句柄。
開始之前
務必確認,
設備驅動器是Microsoft提供的驅動程式:WinUSB(Winusb.sys)。 該驅動程式包含在 \Windows\System32\ 資料夾中。 如需詳細資訊,請參閱 WinUSB (Winusb.sys) 安裝。
您先前已呼叫 WinUsb_Initialize 來取得裝置的 WinUSB 介面句柄。 所有作業都是使用該句柄來執行。 請參閱 如何使用 WinUSB 函式存取 USB 裝置。
作用中的介面設定具有不時針端點。 否則,您無法存取目標端點的管道。
步驟 1:在目前設定中尋找等時管道
- 藉由呼叫 WinUsb_QueryInterfaceSettings,取得具有同步端點的USB介面。
- 列舉定義端點之介面設定的管道。
- 針對每個端點,呼叫 WinUsb_QueryPipeEx,以取得WINUSB_PIPE_INFORMATION_EX結構中的相關聯管道屬性。 擷取的 WINUSB_PIPE_INFORMATION_EX 結構包含關於等時管道的資訊。 結構包含管道、其類型、標識符等相關信息。
- 檢查結構成員,以判斷其是否為必須用於傳輸的管道。 如果是,請儲存 PipeId 值。 在範本程式代碼中,將成員新增至 device.h 中定義的 DEVICE_DATA 結構。
此範例示範如何判斷使用中設定是否有不時針端點,並取得其相關信息。 在此範例中,裝置是 SuperMUTT 裝置。 裝置在預設介面的替代設定 1 中有兩個同步端點。
typedef struct _DEVICE_DATA {
BOOL HandlesOpen;
WINUSB_INTERFACE_HANDLE WinusbHandle;
HANDLE DeviceHandle;
TCHAR DevicePath[MAX_PATH];
UCHAR IsochOutPipe;
UCHAR IsochInPipe;
} DEVICE_DATA, *PDEVICE_DATA;
HRESULT
GetIsochPipes(
_Inout_ PDEVICE_DATA DeviceData
)
{
BOOL result;
USB_INTERFACE_DESCRIPTOR usbInterface;
WINUSB_PIPE_INFORMATION_EX pipe;
HRESULT hr = S_OK;
UCHAR i;
result = WinUsb_QueryInterfaceSettings(DeviceData->WinusbHandle,
0,
&usbInterface);
if (result == FALSE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
printf(_T("WinUsb_QueryInterfaceSettings failed to get USB interface.\n"));
CloseHandle(DeviceData->DeviceHandle);
return hr;
}
for (i = 0; i < usbInterface.bNumEndpoints; i++)
{
result = WinUsb_QueryPipeEx(
DeviceData->WinusbHandle,
1,
(UCHAR) i,
&pipe);
if (result == FALSE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
printf(_T("WinUsb_QueryPipeEx failed to get USB pipe.\n"));
CloseHandle(DeviceData->DeviceHandle);
return hr;
}
if ((pipe.PipeType == UsbdPipeTypeIsochronous) && (!(pipe.PipeId == 0x80)))
{
DeviceData->IsochOutPipe = pipe.PipeId;
}
else if (pipe.PipeType == UsbdPipeTypeIsochronous)
{
DeviceData->IsochInPipe = pipe.PipeId;
}
}
return hr;
}
SuperMUTT 裝置會在預設介面的設定 1 中定義其同步端點。 上述程式代碼會取得 PipeId 值,並將其儲存在 DEVICE_DATA 結構中。
步驟 2:取得等時線管道的間隔資訊
接下來,取得有關您於呼叫 WinUsb_QueryPipeEx 時獲得的管道的詳細資訊。
傳輸大小
從擷取 的 WINUSB_PIPE_INFORMATION_EX 結構中,取得 MaximumBytesPerInterval 和 Interval 值。
根據您想要傳送或接收的不時數數據量,計算傳輸大小。 例如,請考慮此計算:
TransferSize = ISOCH_DATA_SIZE_MS * pipeInfoEx.MaximumBytesPerInterval * (8 / pipeInfoEx.Interval);
在此範例中,傳輸大小會計算為10毫秒的等時數據。
不連續封包數目例如,請考慮此計算:
若要計算保留整個傳輸所需的等時性封包總數。 讀取傳輸時需要此資訊,並計算為
>IsochInTransferSize / pipe.MaximumBytesPerInterval;
。
此範例展示如何在步驟 1 中加入程式碼,並獲取等時管線的間隔值。
#define ISOCH_DATA_SIZE_MS 10
typedef struct _DEVICE_DATA {
BOOL HandlesOpen;
WINUSB_INTERFACE_HANDLE WinusbHandle;
HANDLE DeviceHandle;
TCHAR DevicePath[MAX_PATH];
UCHAR IsochOutPipe;
UCHAR IsochInPipe;
ULONG IsochInTransferSize;
ULONG IsochOutTransferSize;
ULONG IsochInPacketCount;
} DEVICE_DATA, *PDEVICE_DATA;
...
if ((pipe.PipeType == UsbdPipeTypeIsochronous) && (!(pipe.PipeId == 0x80)))
{
DeviceData->IsochOutPipe = pipe.PipeId;
if ((pipe.MaximumBytesPerInterval == 0) || (pipe.Interval == 0))
{
hr = E_INVALIDARG;
printf("Isoch Out: MaximumBytesPerInterval or Interval value is 0.\n");
CloseHandle(DeviceData->DeviceHandle);
return hr;
}
else
{
DeviceData->IsochOutTransferSize =
ISOCH_DATA_SIZE_MS *
pipe.MaximumBytesPerInterval *
(8 / pipe.Interval);
}
}
else if (pipe.PipeType == UsbdPipeTypeIsochronous)
{
DeviceData->IsochInPipe = pipe.PipeId;
if (pipe.MaximumBytesPerInterval == 0 || (pipe.Interval == 0))
{
hr = E_INVALIDARG;
printf("Isoch Out: MaximumBytesPerInterval or Interval value is 0.\n");
CloseHandle(DeviceData->DeviceHandle);
return hr;
}
else
{
DeviceData->IsochInTransferSize =
ISOCH_DATA_SIZE_MS *
pipe.MaximumBytesPerInterval *
(8 / pipe.Interval);
DeviceData->IsochInPacketCount =
DeviceData->IsochInTransferSize / pipe.MaximumBytesPerInterval;
}
}
...
在上述程式代碼中,應用程式會從 WINUSB_PIPE_INFORMATION_EX 取得 Interval 和 MaximumBytesPerInterval,以計算讀取傳輸所需的傳輸大小和非時程封包數目。 在這兩個同步端點中,間隔為1。 該值表示框架的所有微框架都會攜帶數據。 因此,若要傳送 10 毫秒的數據,您需要 10 個畫面格,傳輸大小總計為 10*1024*8 個字節和 80 個不時值封包,每個 1024 個字節長。
步驟 3:進行寫入操作,將數據傳送至同步的 OUT 端點
此程式摘要說明將數據寫入至不時點端點的步驟。
- 配置包含要傳送之數據的緩衝區。
- 如果您要以異步方式傳送資料,請配置並初始化一個 OVERLAPPED 結構,該結構包含由呼叫者配置的事件對象的句柄。 結構必須初始化為零,否則呼叫會失敗。
- 呼叫 WinUsb_RegisterIsochBuffer 來註冊緩衝區。
- 呼叫 WinUsb_WriteIsochPipeAsap 來啟動傳輸。 如果您想要手動指定要傳輸數據的框架,請改為呼叫 WinUsb_WriteIsochPipe 。
- 呼叫 WinUsb_GetOverlappedResult 以取得傳輸的結果。
- 完成後,呼叫 WinUsb_UnregisterIsochBuffer、重疊的事件句柄和傳輸緩衝區,釋放緩衝區句柄。
以下是示範如何傳送寫入傳輸的範例。
#define ISOCH_TRANSFER_COUNT 3
VOID
SendIsochOutTransfer(
_Inout_ PDEVICE_DATA DeviceData,
_In_ BOOL AsapTransfer
)
{
PUCHAR writeBuffer;
LPOVERLAPPED overlapped;
ULONG numBytes;
BOOL result;
DWORD lastError;
WINUSB_ISOCH_BUFFER_HANDLE isochWriteBufferHandle;
ULONG frameNumber;
ULONG startFrame;
LARGE_INTEGER timeStamp;
ULONG i;
ULONG totalTransferSize;
isochWriteBufferHandle = INVALID_HANDLE_VALUE;
writeBuffer = NULL;
overlapped = NULL;
printf(_T("\n\nWrite transfer.\n"));
totalTransferSize = DeviceData->IsochOutTransferSize * ISOCH_TRANSFER_COUNT;
if (totalTransferSize % DeviceData->IsochOutBytesPerFrame != 0)
{
printf(_T("Transfer size must end at a frame boundary.\n"));
goto Error;
}
writeBuffer = new UCHAR[totalTransferSize];
if (writeBuffer == NULL)
{
printf(_T("Unable to allocate memory.\n"));
goto Error;
}
ZeroMemory(writeBuffer, totalTransferSize);
overlapped = new OVERLAPPED[ISOCH_TRANSFER_COUNT];
if (overlapped == NULL)
{
printf("Unable to allocate memory.\n");
goto Error;
}
ZeroMemory(overlapped, (sizeof(OVERLAPPED) * ISOCH_TRANSFER_COUNT));
for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
{
overlapped[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (overlapped[i].hEvent == NULL)
{
printf("Unable to set event for overlapped operation.\n");
goto Error;
}
}
result = WinUsb_RegisterIsochBuffer(
DeviceData->WinusbHandle,
DeviceData->IsochOutPipe,
writeBuffer,
totalTransferSize,
&isochWriteBufferHandle);
if (!result)
{
printf(_T("Isoch buffer registration failed.\n"));
goto Error;
}
result = WinUsb_GetCurrentFrameNumber(
DeviceData->WinusbHandle,
&frameNumber,
&timeStamp);
if (!result)
{
printf(_T("WinUsb_GetCurrentFrameNumber failed.\n"));
goto Error;
}
startFrame = frameNumber + 5;
for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
{
if (AsapTransfer)
{
result = WinUsb_WriteIsochPipeAsap(
isochWriteBufferHandle,
DeviceData->IsochOutTransferSize * i,
DeviceData->IsochOutTransferSize,
(i == 0) ? FALSE : TRUE,
&overlapped[i]);
printf(_T("Write transfer sent by using ASAP flag.\n"));
}
else
{
printf("Transfer starting at frame %d.\n", startFrame);
result = WinUsb_WriteIsochPipe(
isochWriteBufferHandle,
i * DeviceData->IsochOutTransferSize,
DeviceData->IsochOutTransferSize,
&startFrame,
&overlapped[i]);
printf("Next transfer frame %d.\n", startFrame);
}
if (!result)
{
lastError = GetLastError();
if (lastError != ERROR_IO_PENDING)
{
printf("Failed to send write transfer with error %x\n", lastError);
}
}
}
for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
{
result = WinUsb_GetOverlappedResult(
DeviceData->WinusbHandle,
&overlapped[i],
&numBytes,
TRUE);
if (!result)
{
lastError = GetLastError();
printf("Write transfer %d with error %x\n", i, lastError);
}
else
{
printf("Write transfer %d completed. \n", i);
}
}
Error:
if (isochWriteBufferHandle != INVALID_HANDLE_VALUE)
{
result = WinUsb_UnregisterIsochBuffer(isochWriteBufferHandle);
if (!result)
{
printf(_T("Failed to unregister isoch write buffer. \n"));
}
}
if (writeBuffer != NULL)
{
delete [] writeBuffer;
}
for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
{
if (overlapped[i].hEvent != NULL)
{
CloseHandle(overlapped[i].hEvent);
}
}
if (overlapped != NULL)
{
delete [] overlapped;
}
return;
}
步驟 4:發出讀取傳輸請求,以從同步的 IN 端點接收數據。
此程序概述了從同步端點讀取數據的步驟。
- 配置傳輸緩衝區,以在傳輸結束時接收數據。 緩衝區的大小必須以步驟 1 中的傳輸大小計算為基礎。 傳輸緩衝區必須結束於框架界限。
- 如果您要以異步方式傳送數據,請分配一個包含指向呼叫端分配的事件物件句柄的 OVERLAPPED 結構。 結構必須初始化為零,否則呼叫會失敗。
- 呼叫 WinUsb_RegisterIsochBuffer 來註冊緩衝區。
- 根據步驟 2 中計算的等時封包數目,配置等時封包的陣列(USBD_ISO_PACKET_DESCRIPTOR)。
- 呼叫 WinUsb_ReadIsochPipeAsap 來啟動傳輸。 如果您想要手動指定要傳送數據的開始畫面格,請改為呼叫 WinUsb_ReadIsochPipe 。
- 呼叫 WinUsb_GetOverlappedResult 以取得傳輸的結果。
- 完成後,請呼叫 WinUsb_UnregisterIsochBuffer 來釋放緩衝區句柄,並釋放重疊事件句柄、同步封包的陣列,以及傳輸緩衝區。
以下是示範如何呼叫 WinUsb_ReadIsochPipeAsap 和 WinUsb_ReadIsochPipe 來傳送讀取傳輸的範例。
#define ISOCH_TRANSFER_COUNT 3
VOID
SendIsochInTransfer(
_Inout_ PDEVICE_DATA DeviceData,
_In_ BOOL AsapTransfer
)
{
PUCHAR readBuffer;
LPOVERLAPPED overlapped;
ULONG numBytes;
BOOL result;
DWORD lastError;
WINUSB_ISOCH_BUFFER_HANDLE isochReadBufferHandle;
PUSBD_ISO_PACKET_DESCRIPTOR isochPackets;
ULONG i;
ULONG j;
ULONG frameNumber;
ULONG startFrame;
LARGE_INTEGER timeStamp;
ULONG totalTransferSize;
readBuffer = NULL;
isochPackets = NULL;
overlapped = NULL;
isochReadBufferHandle = INVALID_HANDLE_VALUE;
printf(_T("\n\nRead transfer.\n"));
totalTransferSize = DeviceData->IsochOutTransferSize * ISOCH_TRANSFER_COUNT;
if (totalTransferSize % DeviceData->IsochOutBytesPerFrame != 0)
{
printf(_T("Transfer size must end at a frame boundary.\n"));
goto Error;
}
readBuffer = new UCHAR[totalTransferSize];
if (readBuffer == NULL)
{
printf(_T("Unable to allocate memory.\n"));
goto Error;
}
ZeroMemory(readBuffer, totalTransferSize);
overlapped = new OVERLAPPED[ISOCH_TRANSFER_COUNT];
ZeroMemory(overlapped, (sizeof(OVERLAPPED) * ISOCH_TRANSFER_COUNT));
for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
{
overlapped[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (overlapped[i].hEvent == NULL)
{
printf("Unable to set event for overlapped operation.\n");
goto Error;
}
}
isochPackets = new USBD_ISO_PACKET_DESCRIPTOR[DeviceData->IsochInPacketCount * ISOCH_TRANSFER_COUNT];
ZeroMemory(isochPackets, DeviceData->IsochInPacketCount * ISOCH_TRANSFER_COUNT);
result = WinUsb_RegisterIsochBuffer(
DeviceData->WinusbHandle,
DeviceData->IsochInPipe,
readBuffer,
DeviceData->IsochInTransferSize * ISOCH_TRANSFER_COUNT,
&isochReadBufferHandle);
if (!result)
{
printf(_T("Isoch buffer registration failed.\n"));
goto Error;
}
result = WinUsb_GetCurrentFrameNumber(
DeviceData->WinusbHandle,
&frameNumber,
&timeStamp);
if (!result)
{
printf(_T("WinUsb_GetCurrentFrameNumber failed.\n"));
goto Error;
}
startFrame = frameNumber + 5;
for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
{
if (AsapTransfer)
{
result = WinUsb_ReadIsochPipeAsap(
isochReadBufferHandle,
DeviceData->IsochInTransferSize * i,
DeviceData->IsochInTransferSize,
(i == 0) ? FALSE : TRUE,
DeviceData->IsochInPacketCount,
&isochPackets[i * DeviceData->IsochInPacketCount],
&overlapped[i]);
printf(_T("Read transfer sent by using ASAP flag.\n"));
}
else
{
printf("Transfer starting at frame %d.\n", startFrame);
result = WinUsb_ReadIsochPipe(
isochReadBufferHandle,
DeviceData->IsochInTransferSize * i,
DeviceData->IsochInTransferSize,
&startFrame,
DeviceData->IsochInPacketCount,
&isochPackets[i * DeviceData->IsochInPacketCount],
&overlapped[i]);
printf("Next transfer frame %d.\n", startFrame);
}
if (!result)
{
lastError = GetLastError();
if (lastError != ERROR_IO_PENDING)
{
printf("Failed to start a read operation with error %x\n", lastError);
}
}
}
for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
{
result = WinUsb_GetOverlappedResult(
DeviceData->WinusbHandle,
&overlapped[i],
&numBytes,
TRUE);
if (!result)
{
lastError = GetLastError();
printf("Failed to read with error %x\n", lastError);
}
else
{
numBytes = 0;
for (j = 0; j < DeviceData->IsochInPacketCount; j++)
{
numBytes += isochPackets[j].Length;
}
printf("Requested %d bytes in %d packets per transfer.\n", DeviceData->IsochInTransferSize, DeviceData->IsochInPacketCount);
}
printf("Transfer %d completed. Read %d bytes. \n\n", i+1, numBytes);
}
Error:
if (isochReadBufferHandle != INVALID_HANDLE_VALUE)
{
result = WinUsb_UnregisterIsochBuffer(isochReadBufferHandle);
if (!result)
{
printf(_T("Failed to unregister isoch read buffer. \n"));
}
}
if (readBuffer != NULL)
{
delete [] readBuffer;
}
if (isochPackets != NULL)
{
delete [] isochPackets;
}
for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
{
if (overlapped[i].hEvent != NULL)
{
CloseHandle(overlapped[i].hEvent);
}
}
if (overlapped != NULL)
{
delete [] overlapped;
}
return;
}
相關主題
- 如何使用 WinUSB Functions 存取 USB 裝置
- WinUSB 功能