Overlapped I/O
Windows API Reference - Microsoft Developer Network
Table of Contents
Introduction
Overlapped I/O is a crucial feature of the Windows operating system that allows applications to perform input/output operations asynchronously. This means that an application can initiate an I/O operation and then continue to perform other tasks while the I/O operation is in progress. This significantly improves application responsiveness and performance, especially for I/O-bound applications such as servers, network applications, and high-performance data processing tools.
Unlike synchronous I/O, where an application's execution is blocked until the I/O operation completes, overlapped I/O enables concurrency. The operating system handles the I/O operation in the background, and the application is notified when the operation is finished.
How Overlapped I/O Works
The core concept behind overlapped I/O is the ability to initiate an operation and then proceed without waiting for its completion. This is achieved through the use of the OVERLAPPED structure and specific I/O functions that support asynchronous operations. When an overlapped I/O operation is initiated, the system queues the request and returns immediately. The application can then choose to poll for completion, wait on an event, or use other synchronization mechanisms to be notified when the I/O operation is done.
Key Structures and Functions
The OVERLAPPED Structure
The OVERLAPPED structure is central to overlapped I/O. It contains information necessary for the operating system to manage the asynchronous operation, including:
hEvent: An optional event handle that the system signals when the operation completes. If this member isNULL, you must use an alternative completion notification mechanism.OffsetandOffsetHigh: Specify the file offset at which to begin the I/O operation.- Internal members: Reserved for system use.
typedef struct _OVERLAPPED {
DWORD_PTR Internal;
DWORD_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
};
PVOID Pointer;
};
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
Creating Overlapped I/O Handles
To use overlapped I/O, you must create or open a handle with the FILE_FLAG_OVERLAPPED flag set. This is typically done using functions like CreateFile.
HANDLE hFile = CreateFile(
L"mydata.txt",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL
);
Initiating Overlapped Operations
Functions like ReadFile, WriteFile, ReadFileEx, and WriteFileEx are used to initiate overlapped I/O operations. You must pass a pointer to an initialized OVERLAPPED structure.
OVERLAPPED ov;
ZeroMemory(&ov, sizeof(ov));
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // Create an event object
char buffer[1024];
DWORD bytesRead;
BOOL success = ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, &ov);
if (!success) {
DWORD error = GetLastError();
if (error == ERROR_IO_PENDING) {
// I/O operation is in progress
// Continue other work...
} else {
// Handle other errors
}
} else {
// I/O operation completed synchronously
// Process bytesRead
}
Completing Overlapped Operations
Once an overlapped operation is initiated, you need a way to determine when it's finished. Common methods include:
- Polling with
GetOverlappedResult: This function can be used to check the status of an operation. - Waiting on the
hEvent: If you provided a valid event handle in theOVERLAPPEDstructure, you can use functions likeWaitForSingleObject. - I/O Completion Ports (IOCP): For high-performance scenarios, IOCPs offer a scalable way to manage multiple concurrent I/O operations.
// Using WaitForSingleObject
if (WaitForSingleObject(ov.hEvent, INFINITE) == WAIT_OBJECT_0) {
DWORD bytesTransferred;
if (GetOverlappedResult(hFile, &ov, &bytesTransferred, FALSE)) {
// Operation completed successfully, process bytesTransferred
} else {
// Handle error
}
}
Synchronization Methods
Effective synchronization is key to managing overlapped I/O correctly. Besides the event object in OVERLAPPED, you can use:
WaitForMultipleObjects: To wait on multiple events or handles simultaneously.- I/O Completion Ports (IOCP): A highly efficient mechanism for managing large numbers of asynchronous I/O operations. Applications create a completion port and associate file handles with it. When an I/O operation completes, the system posts a completion packet to the port, allowing a dedicated thread pool to process completed I/O requests.
Best Practices
- Always create handles with
FILE_FLAG_OVERLAPPEDfor overlapped I/O. - Initialize the
OVERLAPPEDstructure for each operation. - Manage event handles properly; create them, use them, and close them when no longer needed.
- When using
GetOverlappedResult, passFALSEfor thebWaitparameter if you have already waited on the event. - Consider I/O Completion Ports for high-performance server applications to maximize throughput and scalability.
- Ensure that the buffer passed to I/O functions remains valid until the operation completes.
Example
This example demonstrates reading from a file asynchronously using ReadFile and an event object.
#include <windows.h>
#include <iostream>
int main() {
HANDLE hFile = CreateFile(
L"sample.txt",
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
std::cerr << "Error opening file: " << GetLastError() << std::endl;
return 1;
}
OVERLAPPED ov;
ZeroMemory(&ov, sizeof(ov));
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
char buffer[256];
DWORD bytesRead;
std::cout << "Initiating asynchronous read..." << std::endl;
if (!ReadFile(hFile, buffer, sizeof(buffer) - 1, &bytesRead, &ov)) {
DWORD error = GetLastError();
if (error != ERROR_IO_PENDING) {
std::cerr << "Error in ReadFile: " << error << std::endl;
CloseHandle(ov.hEvent);
CloseHandle(hFile);
return 1;
}
std::cout << "Read operation is pending..." << std::endl;
} else {
std::cout << "Read operation completed synchronously." << std::endl;
}
// Do other work here while I/O is pending...
std::cout << "Doing other work..." << std::endl;
std::cout << "Waiting for I/O to complete..." << std::endl;
if (WaitForSingleObject(ov.hEvent, INFINITE) == WAIT_OBJECT_0) {
DWORD bytesTransferred;
if (GetOverlappedResult(hFile, &ov, &bytesTransferred, FALSE)) {
buffer[bytesTransferred] = '\0'; // Null-terminate the buffer
std::cout << "Read successful. Bytes transferred: " << bytesTransferred << std::endl;
std::cout << "Content: " << buffer << std::endl;
} else {
std::cerr << "Error in GetOverlappedResult: " << GetLastError() << std::endl;
}
} else {
std::cerr << "WaitForSingleObject failed." << std::endl;
}
CloseHandle(ov.hEvent);
CloseHandle(hFile);
return 0;
}