從 WinUSB 傳統型應用程式傳送 USB 時序傳輸
從 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;
}