Win32 File I/O

Low-Level File Operations

File Streams

File streams represent a sequence of bytes that can be read from or written to a file. The Win32 API provides a set of functions for low-level file input and output operations, allowing for fine-grained control over file access, buffering, and error handling. Understanding file streams is fundamental to performing efficient and robust file operations within Windows applications.

Core Concepts

When you open a file in Win32, you are essentially creating a file stream. This stream is associated with a unique identifier called a file handle. This handle is used in subsequent operations to interact with the file.

Key Functions for File Streams

Creating and Opening Files:

The primary function for creating or opening files is CreateFile. It offers extensive control over how a file is accessed.


HANDLE CreateFile(
    LPCTSTR lpFileName,              // File name
    DWORD dwDesiredAccess,           // Desired access (e.g., GENERIC_READ, GENERIC_WRITE)
    DWORD dwShareMode,               // Sharing mode (e.g., FILE_SHARE_READ)
    LPSECURITY_ATTRIBUTES lpSecurityAttributes, // Security attributes
    DWORD dwCreationDisposition,     // How to create or open
    DWORD dwFlagsAndAttributes,      // File attributes and flags
    HANDLE hTemplateFile             // Template file handle
);
            

Upon successful execution, CreateFile returns a valid file handle. If the operation fails, it returns INVALID_HANDLE_VALUE.

Reading from File Streams:

The ReadFile function is used to read data from an open file stream into a buffer.


BOOL ReadFile(
    HANDLE hFile,                    // Handle to the file
    LPVOID lpBuffer,                 // Buffer to store data
    DWORD nNumberOfBytesToRead,      // Number of bytes to read
    LPDWORD lpNumberOfBytesRead,     // Number of bytes actually read
    LPOVERLAPPED lpOverlapped        // For asynchronous I/O
);
            

lpNumberOfBytesRead will be set to the number of bytes that were actually read. If it's less than nNumberOfBytesToRead, it might indicate an end-of-file condition or an error.

Writing to File Streams:

Use the WriteFile function to write data from a buffer to an open file stream.


BOOL WriteFile(
    HANDLE hFile,                    // Handle to the file
    LPCVOID lpBuffer,                // Buffer containing data to write
    DWORD nNumberOfBytesToWrite,     // Number of bytes to write
    LPDWORD lpNumberOfBytesWritten,  // Number of bytes actually written
    LPOVERLAPPED lpOverlapped        // For asynchronous I/O
);
            

Similar to ReadFile, lpNumberOfBytesWritten indicates the actual number of bytes written.

Closing File Streams:

It's crucial to close file handles when they are no longer needed to release system resources and ensure all buffered data is flushed. Use the CloseHandle function.


BOOL CloseHandle(
    HANDLE hObject // Handle to close
);
            

Note that CloseHandle is a general-purpose function for closing various system objects, including file handles.

Common Scenarios and Considerations

Buffering
Win32 file I/O can be buffered or unbuffered. Buffered I/O generally offers better performance by reducing the number of direct system calls. The system handles the buffering automatically for most operations.
Error Handling
Always check the return values of file I/O functions. Use GetLastError() to retrieve detailed error codes when a function fails.
Asynchronous I/O
For performance-critical applications, asynchronous I/O using OVERLAPPED structures can prevent your application from blocking while waiting for I/O operations to complete. This is achieved by passing an OVERLAPPED pointer to ReadFile or WriteFile.
File Pointers
You can manipulate the current position within a file stream using functions like SetFilePointerEx (recommended) or SetFilePointer.

Example: Simple File Read

Here's a basic example demonstrating how to read content from a file:


#include <windows.h>
#include <iostream>
#include <vector>

int main() {
    HANDLE hFile = CreateFile(
        L"my_document.txt",
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        std::cerr << "Error opening file. Error code: " << GetLastError() << std::endl;
        return 1;
    }

    const DWORD BUFFER_SIZE = 1024;
    std::vector<char> buffer(BUFFER_SIZE);
    DWORD bytesRead;

    std::cout << "Reading from file..." << std::endl;

    while (ReadFile(hFile, buffer.data(), BUFFER_SIZE, &bytesRead, NULL) && bytesRead > 0) {
        std::cout.write(buffer.data(), bytesRead);
    }

    if (GetLastError() != ERROR_SUCCESS && GetLastError() != ERROR_HANDLE_EOF) {
        std::cerr << "Error reading file. Error code: " << GetLastError() << std::endl;
    }

    CloseHandle(hFile);
    std::cout << std::endl << "Finished reading." << std::endl;

    return 0;
}
            
← File Attributes Directory Management →