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.
- File Handles: A handle is an opaque identifier that the operating system uses to refer to a file object. It's not a direct pointer to the file data but rather a reference to an internal structure managed by the OS.
- Access Modes: When opening a file, you specify the desired access modes (e.g., read, write, read-write). This determines what operations are permissible on the stream.
- Sharing Modes: These modes control how other processes can access the file concurrently.
- Creation Disposition: This specifies whether to create a new file, open an existing one, or both.
- Flags and Attributes: Various flags can be used to control aspects like caching, asynchronous I/O, and file attributes.
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
OVERLAPPEDstructures can prevent your application from blocking while waiting for I/O operations to complete. This is achieved by passing anOVERLAPPEDpointer toReadFileorWriteFile. - File Pointers
- You can manipulate the current position within a file stream using functions like
SetFilePointerEx(recommended) orSetFilePointer.
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;
}