使用文件设备 因为文件的使用非常普遍,我将花一些时间来专门讨论适用于文件设备的一些要点。本节讨论了如何定位文件指针和改变文件大小。 你必须明白的第一个要点是Windows被设计为可操作非常大的文件。微软的设计师们原本就选用了64-bit的数值来描述一个文件的大小,而不是32-bit。这就意味着一个文件理论上可达到16EB(exabytes)。 在 一个32-bit的操作系统上处理64-bit的数值,使得操作文件有点令人不快,因为许多Windows函数要求你以两个独立的32-bit数值来传递 64-bit参数。不过你会发现,使用这些参数并不是很困难,而且在日常操作中,你不太可能会使用一个大于4GB的文件。也就是说表示一个文件大小 的 64-bit数值的高32-bit通常总是为0。 取得文件大小 当你使用文件时,经常需要获取文件的大小。最简单的方法是调用GetFileSizeEx: BOOL GetFileSizeEx( HANDLE hfile, PLARGE_INTEGER pliFileSize); 第 一个参数hfile是文件的句柄,pliFileSize参数是一个LARGE_INTEGER联合体的地址。LARGE_INTEGER联合使得一 个 64-bit数既可以作为两个32-bit的数值,也可以作为一个单独的64-bit数值来引用,这在计算文件大小和偏移值时是相当方便的。这个联合 看起来(基本上)如下所示: typedef union _LARGE_INTEGER { struct { DWORD LowPart; // Low 32-bit unsigned value LONG HighPart; // High 32-bit signed value }; LONGLONG QuadPart; // Full 64-bit signed value } LARGE_INTEGER, *PLARGE_INTEGER; 除了LARGE_INTEGER外,还有ULARGE_INTEGER联合用来描述一个无符号的64-bit数值: typedef union _ULARGE_INTEGER { struct { DWORD LowPart; // Low 32-bit unsigned value DWORD HighPart; // High 32-bit unsigned value }; ULONGLONG QuadPart; // Full 64-bit unsigned value } ULARGE_INTEGER, *PULARGE_INTEGER; 另外一个很有用的获取文件大小的函数是GetCompressedFileSize: DWORD GetCompressedFileSize( PCTSTR pszFileName, PDWORD pdwFileSizeHigh); 这 个函数返回文件的物理大小,而GetFileSizeEx返回的是文件的逻辑大小。例如,考虑一个100-KB的文件被压缩后占用85 KB空间。调用 GetFileSizeEx返回的是文件的逻辑大小100 KB,而GetCompressedFileSize返回的是文件在磁盘上实际占用的字节数 85 KB。 与GetFileSizeEx不同的是,GetCompressedFileSize的第一个参数用一个文件名作为 字符串传递而不是使用句柄。 GetCompressedFileSize函数以一种与众不同的方式返回64-bit的数值:文件大小的低32-bit就 是函数的返回值,高32- bit被放置于pdwFileSizeHigh参数指向的DWORD变量中。下面是ULARGE_INTEGER联合常见的用 法: ULARGE_INTEGER ulFileSize; ulFileSize.LowPart = GetCompressedFileSize("SomeFile.dat", &ulFileSize.HighPart); // 64-bit file size is now in ulFileSize.QuadPart 定位文件指针 调 用CreateFile会使系统创建一个文件内核对象来管理与文件相关的各种操作。这个内核对象的内幕是一个文件指针。文件指针就是一个文件内的 64- bit偏移值,指示下一次同步读写的位置。开始时,文件指针被设为0,所以如果你在调用CreateFile后立即调用ReadFile,你将从 文件的偏移0处开始读。如果你从文件读了10字节到内存,系统会更新与文件句柄关联的指针,使得下次调用ReadFile时,从文件10字节处开始读。例 如,看一下这段代码,其中文件的开始10字节被读入缓存,接着又把随后的10字节读入缓存: BYTE pb[10]; DWORD dwNumBytes; HANDLE hfile = CreateFile("MyFile.dat", ...); // Pointer set to 0 ReadFile(hfile, pb, 10, &dwNumBytes, NULL); // Reads bytes 0 - 9 ReadFile(hfile, pb, 10, &dwNumBytes, NULL); // Reads bytes 10 - 19 因为每一个文件内核对象有自己的文件指针,故两次打开同一个文件会有稍微不同的结果: BYTE pb[10]; DWORD dwNumBytes; HANDLE hfile1 = CreateFile("MyFile.dat", ...); // Pointer set to 0 HANDLE hfile2 = CreateFile("MyFile.dat", ...); // Pointer set to 0 ReadFile(hfile1, pb, 10, &dwNumBytes, NULL); // Reads bytes 0 - 9 ReadFile(hfile2, pb, 10, &dwNumBytes, NULL); // Reads bytes 0 - 9 在这个例子中,两个不同的内核对象管理同一个文件。由于每个内核对象都有自己的文件指针,用一个文件对象操作文件时不会影响另一个对象维护的文件指针,故文件开始处的10字节会被读两次。 我想多举一个例子以使得这些内容更清晰: BYTE pb[10]; DWORD dwNumBytes; HANDLE hfile1 = CreateFile("MyFile.dat", ...); // Pointer set to 0 HANDLE hfile2; DuplicateHandle( GetCurrentProcess(), hfile1, GetCurrentProcess(), &hfile2, 0, FALSE, DUPLICATE_SAME_ACCESS); ReadFile(hfile1, pb, 10, &dwNumBytes, NULL); // Reads bytes 0 - 9 ReadFile(hfile2, pb, 10, &dwNumBytes, NULL); // Reads bytes 10 - 19 在这个例子中,一个文件内核对象被两个文件句柄引用。不管用哪一个句柄操作文件,都只有一个文件指针被更新。和上个例子一样,每次都读出不同的字节。 如果你需要随机访问一个文件,你就需要改变与文件内核对象相关联的文件指针。调用SetFilePointerEx可以做到这一点: BOOL SetFilePointerEx( HANDLE hfile, LARGE_INTEGER liDistanceToMove, PLARGE_INTEGER pliNewFilePointer, DWORD dwMoveMethod); hfile 参数指示你想改变其文件指针的文件内核对象。iDistanceToMove参数告诉系统你想移动指针多少个字节。你指定的数字是增加到文件指针的当前 值,所以一个负数将引起文件指针的倒退。SetFilePointerEx的最后一个参数,dwMoveMethod,告 诉 SetFilePointerEx如何解释liDistanceToMove参数。表 2-8 描述了你可以传递给dwMoveMethod的三个不 同值以指定移动的起始点。 表 2-8.可传递给SetFilePointerEx的dwMoveMethod参数的值 参数值 含义 FILE_BEGIN 文件对象的文件指针被设为liDistanceToMove参数给出的值。注意liDistanceToMove被当成一个无符号的64-bit值。 FILE_CURRENT 文件对象的文件指针的值被加上liDistanceToMove。注意liDistanceToMove被当成一个有符号的64-bit值,你可以在文件中向后移动指针。 FILE_END 文件对象的文件指针被设为文件逻辑大小加上liDistanceToMove的值。注意liDistanceToMove被当成一个有符号的64-bit值,你可以在文件中向后移动指针。 在SetFilePointerEx更新文件对象的文件指针后,文件指针的新值被返回到由pliNewFilePointer参数指向的LARGE_INTEGER。如果你对新指针值不感兴趣,可以传递NULL给pliNewFilePointer。 关于SetFilePointerEx这里给出一些要注意的要点: ■ 设置文件指针越过文件的当前长度末尾是合法的。这样做并不会实际增加文件在磁盘上的大小,除非你在这个位置写文件或调用SetEndOfFile。 ■ 对由FILE_FLAG_NO_BUFFERING标志打开的文件使用SetFilePointerEx时,文件指针只能被定位于对齐扇区的边界处。本章后面的FileCopy示例程序演示了如何正确使用这种方式。 ■ Windows没有提供一个GetFilePointerEx函数,但是你可以使用SetFilePointerEx来获得期望的效果: LARGE_INTEGER liCurrentPosition = { 0 }; SetFilePointerEx(hfile, liCurrentPosition, &liCurrentPosition, FILE_CURRENT); 设置文件尾 通常,系统在文件关闭时负责设置文件尾。不过,你可能有时想要使一个文件变大或变小。在这些情况下,调用 BOOL SetEndOfFile(HANDLE hfile); SetEndOfFile截断或延伸一个文件的末尾到文件对象的指针所指示的位置。例如,如果你想强制一个文件为1024字节长,你可以这样使用SetEndOfFile: HANDLE hfile = CreateFile(...); LARGE_INTEGER liDistanceToMove; liDistanceToMove.QuadPart = 1024; SetFilePointerEx(hfile, liDistanceToMove, NULL, FILE_BEGIN); SetEndOfFile(hfile); CloseHandle(hfile); 用Windows Explorer查看该文件的属性,你会看到文件正好是1024字节长。 |
|