Overlapped I/O

Windows API Reference - Microsoft Developer Network

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.

Note: Overlapped I/O is primarily used with file handles, pipe handles, serial communication devices, and sockets. Not all handles support overlapped I/O.

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:

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:

// 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:

Best Practices

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;
}