第6章 Overlapped I/O, 在你身后变戏法 ---Win32 文件操作函数 -2
Win32 之中有三个基本的函数用来执行 I/O,它们是:
i CreateFile()
i ReadFile()
i WriteFile()
没有另外哪一个函数用来关闭文件,只要调用 CloseHandle() 即可。本章对于这些函数将只涵盖其与 overlapped I/O 有关的部分,至于其他和文件 I/O有关的部分,请参考 Win32 Programmer's Reference。
CreateFile() 可以用来打开各式各样的资源,包括(但不限制于):
i 文件(硬盘、软盘、光盘或其他)
i 串行口和并行口(serial and parallel ports)
i Named pipes
i Console(请看第8章)
CreateFile() 的函数原型看起来像这样:
HANDLE CreateFile(
LPCTSTR lpFileName, // 指向文件名称
DWORD dwDesiredAccess, // 存取模式(读或写)
DWORD dwShareMode, // 共享模式(share mode)
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向安全属性结构
DWORD dwCreationDisposition, // 如何产生
DWORD dwFlagsAndAttributes, // 文件属性
HANDLE hTemplateFile // 一个临时文件,将拥有全部的属性拷贝
);
其中第6个参数 dwFlagsAndAttributes 是使用 overlapped I/O 的关键。这个参数可以藉由许多个数值组合在一起而完成,其中对于本处讨论最重要的一个数值便是 FILE_FLAG_OVERLAPPED。你可以藉着这个参数,指定使用同 步(传统的)调用,或是使用 overlapped(异步)调用,但不能够两个都指定。换句话说,如果这个标记值设立,那么对该文件的每一个操作都将是overlapped。
一个不常被讨论的 overlapped I/O 性质是,它可以在同一时间读(或写)文件的许多部分。微妙处在于这些操作都使用相同的文件 handle。因此,当你使用 overlapped I/O 时,没有所谓“目前的文件位置”这样的观念。每一次读或写的操作都必须包含其文件位置。
如果你发出许多个 overlapped 请求,那么执行次序无法保证。虽然你在单一磁盘中对文件进行操作时很少会有这样的行为,但如果面对多个磁盘,或不同种类的设备(如网络和磁盘),就常常会看到 I/O 请求完全失去次序。
你将不可能藉由调用 C runtime library 中的 stdio.h 函数而使用overlapped I/O。因此,没有很方便的方法可以实现 overlapped text-based I/O。例如,fgets() 允许你一次读取一行文字,但你不能够使用 fgets()、fprintf() 或任何其他类似的 C runtime 函数来进行 overlapped I/O。
Overlapped I/O 的基本型式是以 ReadFile() 和 WriteFile() 完成的。这个函数的原型如下:
BOOL ReadFile(
HANDLE hFile, // 欲读之文件
LPVOID lpBuffer, // 接收数据之缓冲区
DWORD nNumberOfBytesToRead, // 欲读取的字节个数
LPDWORD lpNumberOfBytesRead, // 实际读取的字节个数的地址
LPOVERLAPPED lpOverlapped // 指针,指向 overlapped info
);
BOOL WriteFile(
HANDLE hFile, // 欲写之文件
LPCVOID lpBuffer, // 储存数据之缓冲区
DWORD nNumberOfBytesToWrite, // 欲写入的字节个数
LPDWORD lpNumberOfBytesWritten, // 实际写入的字节个数的地址
LPOVERLAPPED lpOverlapped // 指针,指向 overlapped info
);
这两个函数很像 C runtime 函数中的 fread() 和 fwrite(),差别在于最后一个参数lpOverlapped 。如果CreateFile() 的第6 个参数被指定为FILE_FLAG_ OVERLAPPED,你就必须在上述的 lpOverlapped 参数中提供一个指针,指向一个 OVERLAPPED 结构。
OVERLAPPED 结构
OVERLAPPED 结构执行两个重要的功能。第一,它像一把钥匙,用以识别每一个目前正在进行的 overlapped 操作。第二,它在你和系统之间提供了一个共享区域,参数可以在该区域中双向传递。
OVERLAPPED 结构看起来像这样:
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
OVERLAPPED 结构中的成员
表格6-1 描述 OVERLAPPED 结构中的每一个成员。
表格6-1 OVERLAPPED 结构中的成员(栏位)
成员名称 说 明
Internal 通常它被保留。然而当 GetOverlappedResult() 传回 FALSE 并且GetLastError() 并非传回 ERROR_IO_PENDING 时,这个栏位将内含一个视系统而定的状态
InternalHigh 通常它被保留。然而当 GetOverlappedResult() 传回 TRUE 时,这个栏位将内含“被传输数据的长度”
Offset 文件之中开始被读或被写的偏移位置(以字节为单位)。该偏移位置从文件头开始起算。如果目标设备(例如 pipes)并没有支持文件位置,此栏位将被忽略
OffsetHigh 64 位的文件偏移位置中,较高的 32 位。如果目标设备(例如 pipes)并没有支持文件位置,此栏位将被忽略
hEvent 一个手动重置(manual-reset)的 event 对象,当 overlapped I/O 完成时即被激发。ReadFileEx() 和 WriteFileEx() 会忽略这个栏位,彼时它可能被用来传递一个用户自定义的指针
由于 OVERLAPPED 结构的生命期超越 ReadFile() 和 WriteFile() 函数,所以把这个结构放在一个安全的地方是很重要的事情。通常局部变量并不是一个安全的地方,因为它会很快就越过了生存范围(out of scope)。最安全的地方就是 heap。
现在我们有了所有的基础物质,让我们看看如何运用它们。