第11.14章: Win32 文件 API
概述
在 Windows 上,用户态应用通过 kernel32.dll 与内核打交道。打开文件的两个入口是:
-
CreateFileW— 宽字符(UTF-16)变体,每个现代 Windows 版本(NT、2000、XP、7、10、11)都用它。 -
CreateFileA— ANSI 变体,在 Windows 9x 家族(95、98、ME)上还会遇到。
fast_io 用两个类型把这个差异藏起来,与 POSIX 层一一对应:
-
::fast_io::win32_file— RAII 拥有式包装,构造函数调用CreateFileW(9x 上则调用CreateFileA),析构函数调用CloseHandle。 -
::fast_io::win32_io_observer— 对已有HANDLE的非拥有视图。可平凡复制,没有析构函数。
在 ANSI 与 Wide 之间选择:win32_api_family
Win32 层暴露一个名为 ::fast_io::win32_api_family 的特征类型。它选择在给定 Windows 目标上该用哪一套 API:
-
在 NT 家族上(今天所有受支持的编译器的默认值),该家族选用
CreateFileW以及宽字符重载。 -
在 9x 家族上,该家族选用
CreateFileA以及 ANSI 重载。
实践中你几乎不需要直接接触 win32_api_family:当你传入文件名时,win32_file 会自动查询它。
用 win32_file 打开文件
#include <fast_io.h>
#include <fast_io_device.h>
int main()
{
using namespace ::fast_io::iomnp;
// The constructor picks CreateFileW or CreateFileA through
// ::fast_io::win32_api_family.
::fast_io::win32_file wf(u8"hello_win32.txt", ::fast_io::open_mode::out);
print(wf, "Hello from win32_file!\n");
::std::size_t const n{42zu};
println(wf, "The answer is: ", n);
// CloseHandle is invoked automatically when `wf` goes out of scope.
}
包裹已有的 HANDLE
与 posix_io_observer 一样,你可以对别处取得的 HANDLE 构建非拥有视图:
#include <fast_io.h>
#include <fast_io_device.h>
void write_via_handle(void* h)
{
using namespace ::fast_io::iomnp;
// Non-owning: does NOT call CloseHandle on destruction.
::fast_io::win32_io_observer observer{
static_cast<::fast_io::win32_io_observer::native_handle_type>(h)};
print(observer, "Written through a win32_io_observer\n\n");
}
int main()
{
::fast_io::win32_file wf(u8"legacy_handle.txt", ::fast_io::open_mode::out);
write_via_handle(wf.native_handle());
}
读取与 open-at
读取使用 scan。相对于目录句柄打开时,底层使用 NtCreateFile,因为 Win32 API 本身没有 *at 的对应物:
#include <fast_io.h>
#include <fast_io_device.h>
int main()
{
using namespace ::fast_io::iomnp;
::fast_io::win32_file inpf(u8"input.txt", ::fast_io::open_mode::in);
::std::size_t value{};
::fast_io::io::scan(inpf, value);
::fast_io::win32_file outpf(u8"output.txt", ::fast_io::open_mode::out);
println(outpf, "Read the value: ", value);
}
Windows 也有文件描述符
一个常见的误解是 Windows 只有 HANDLE 而没有文件描述符。这是不对的。Windows C 运行时(MSVCRT、UCRT)通过 _fdopen、_open、_read、_write、_close 等函数提供类 POSIX 的文件描述符,它们声明在 <io.h> 与 <fcntl.h> 中。
C 运行时维护一张把文件描述符(小整数)映射到 Win32 HANDLE 的表,你可以在两者之间转换:
-
_get_osfhandle(fd)— 把文件描述符转换为 Win32HANDLE。参见 Microsoft 文档。 -
_open_osfhandle(handle, flags)— 把 Win32HANDLE转换为文件描述符。 -
_fdopen(fd, mode)— 把 C 的FILE*流关联到文件描述符上。参见 Microsoft 文档。
#include <fast_io.h>
#include <fast_io_device.h>
#include <io.h>
#include <windows.h>
int main()
{
using namespace ::fast_io::iomnp;
// Windows C runtime file descriptor (not a HANDLE!)
int fd{::_open("data.txt", _O_RDONLY)};
if (fd == -1) {
// handle error
}
// Convert file descriptor to Win32 HANDLE
void* h{::_get_osfhandle(fd)};
// Now you can use Win32 APIs on it
::fast_io::win32_io_observer observer{
static_cast<::fast_io::win32_io_observer::native_handle_type>(h)};
println(observer, "Using Win32 API on a C runtime fd");
// Close the file descriptor (this also closes the underlying HANDLE)
::_close(fd);
}
这证明Windows 既有文件描述符也有 HANDLE。C 运行时在 Win32 API 之上提供了一层类 POSIX 的包装。当你调用 _read(fd, ...) 时,C 运行时内部调用的是 ReadFile(_get_osfhandle(fd), ...)。
原始的 Win32 I/O API
在使用 fast_io 的包装之前,先了解底层 Win32 API 会有帮助。这些是由 kernel32.dll 导出的函数:
| API 函数 | 用途 |
|---|---|
CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile) |
按名字打开或创建文件(宽字符 / UTF-16),返回 HANDLE。 |
CreateFileA(...) |
CreateFileW 的 ANSI 变体,用于 Windows 9x(95、98、ME)。 |
ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped) |
从文件句柄读取字节。返回 BOOL;实际读到的字节数写入 lpNumberOfBytesRead。 |
WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped) |
向文件句柄写入字节。返回 BOOL;实际写入的字节数写入 lpNumberOfBytesWritten。 |
CloseHandle(hObject) |
关闭句柄。永远需要 — 句柄是有限资源。 |
SetFilePointer(hFile, lDistanceToMove, lpDistanceToMoveHigh, dwMoveMethod) |
改变文件指针。dwMoveMethod 是 FILE_BEGIN、FILE_CURRENT 或 FILE_END。对大文件请用 SetFilePointerEx。 |
GetFileSize(hFile, lpFileSizeHigh) |
取得文件大小。对大文件请用 GetFileSizeEx。 |
这些 Win32 API 不带缓冲 — 每次调用都经 kernel32.dll 进入内核。这使得它们在操作很小或很频繁时很慢。fast_io 的带缓冲类型(如 ibuf_file 与 obuf_file)用用户态缓冲包装这些调用,性能好得多。
即使不用 fast_io 的 I/O,也可以用 win32_file 做 RAII
即使你更愿意直接调用原始 Win32 API(例如 ReadFile 与 WriteFile)而不使用 fast_io 的 print 与 scan,你仍然应该用 ::fast_io::win32_file 做 RAII。它的析构函数保证调用 CloseHandle,所以你永远不会忘记关闭句柄。
当涉及异常或提前返回时,这一点尤其重要 — 没有 RAII,你必须在每一条退出路径上手动调用 CloseHandle,这很容易出错。
#include <fast_io.h>
#include <fast_io_device.h>
#include <windows.h>
int main()
{
// Open with win32_file for RAII, but use raw Win32 ReadFile/WriteFile
::fast_io::win32_file wf(u8"data.bin", ::fast_io::open_mode::out);
// Get the raw HANDLE
void* h{wf.native_handle()};
// Use raw Win32 WriteFile (unbuffered, goes through kernel32.dll)
char const buffer[]{"Hello from raw WriteFile\n"};
DWORD written{};
BOOL ok{::WriteFile(h, buffer, static_cast<DWORD>(sizeof(buffer) - 1), &written, nullptr)};
if (!ok || written != sizeof(buffer) - 1) {
// handle error
}
// wf's destructor calls CloseHandle(h) automatically when it goes out of scope.
// No need to manually call CloseHandle() here, even if we return early or throw.
}
同样的原则也适用于读取:
#include <fast_io.h>
#include <fast_io_device.h>
#include <windows.h>
int main()
{
::fast_io::win32_file wf(u8"data.bin", ::fast_io::open_mode::in);
void* h{wf.native_handle()};
char buffer[256];
DWORD nread{};
BOOL ok{::ReadFile(h, buffer, sizeof(buffer), &nread, nullptr)};
if (ok && nread > 0) {
// Process the buffer
}
// CloseHandle(h) is called automatically
}
要定位,就在原生句柄上使用 SetFilePointerEx:
#include <fast_io.h>
#include <fast_io_device.h>
#include <windows.h>
int main()
{
::fast_io::win32_file wf(u8"data.bin",
::fast_io::open_mode::in | ::fast_io::open_mode::out);
void* h{wf.native_handle()};
LARGE_INTEGER distanceToMove;
distanceToMove.QuadPart = 100;
// Seek to offset 100 from the beginning of the file
BOOL ok{::SetFilePointerEx(h, distanceToMove, nullptr, FILE_BEGIN)};
if (!ok) {
// handle error
}
// Now read or write from the new position
}
小结
-
::fast_io::win32_file用 RAII 包装 Win32 的CreateFileW/CreateFileA入口。 -
::fast_io::win32_io_observer是对已有HANDLE的非拥有视图。 -
::fast_io::win32_api_family根据目标 Windows 家族在 ANSI 与 Wide 变体之间选择。通常你不需要直接接触它。 -
fast_io的所有标准算法 —print、println、scan— 都能作用于这些类型。 -
Windows 也有文件描述符,由 C 运行时(MSVCRT/UCRT)提供,而不只有 Win32
HANDLE。用_get_osfhandle(fd)把文件描述符转成HANDLE,用_open_osfhandle(handle, flags)转回来。 -
原始 Win32 API 有
CreateFileW(9x 上是CreateFileA)、ReadFile、WriteFile、CloseHandle、SetFilePointerEx与GetFileSizeEx。它们不带缓冲,都经过kernel32.dll。 -
即使你直接调用原始 Win32 API(例如
ReadFile与WriteFile),也仍然要用::fast_io::win32_file做 RAII,以保证CloseHandle被自动调用。这能防止资源泄漏,在有异常或提前返回时尤其如此。 - 关于 Win32 文件 API 的更多细节,见 Microsoft 文档。