问题是,当我禁用缓存(在CreateFile
中设置FILE_FLAG_NO_BUFFERING
)时,我必须向读写函数传递512(扇区大小)的倍数的字节数。我使用10MB的缓冲区,一切正常……直到最后一次缓冲操作。最后一个缓冲区的字节数不是512的倍数。那么,我该如何读写文件的最后一部分呢?
这是我迄今为止写的。。。
procedure MarusCopyFileNoCache(SrcName,DestName:string);
const BufSize = 10485760; {10MB}
var Src,Dest:THandle;
Buffer:Pointer;
Bufs,x:integer;
AllSize:int64;
N,junk:DWord;
begin
Src:=CreateFileW(PWideChar('\?'+SrcName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
Dest:=CreateFileW(PWideChar('\?'+DestName), GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_FLAG_NO_BUFFERING, 0);
try
AllSize:=MFileSize(Src); {this is my function to get the int64 file size}
Bufs:=Ceil(AllSize/BufSize);
GetMem(Buffer,BufSize);
try
for x:=1 to Bufs do begin
ReadFile(Src, Buffer^, BufSize, N, nil);
WriteFile(Dest, Buffer^, N, junk, nil);
end;
finally
FreeMem(Buffer,BufSize);
end;
finally
CloseHandle(Src);
CloseHandle(Dest);
end;
end;
使用FILE_FLAG_NO_BUFFERING
时,必须读取和写入完整扇区,但最后一个缓冲区将是部分扇区(ReadFile()
报告的读取字节数将少于完整扇区大小)。当您写入最后一个缓冲区时,您仍然必须将其写入完整的扇区,否则WriteFile()
将失败。很明显,输出文件的大小可能太大了。但是,由于您确实知道所需的文件大小,因此可以在完成对输出文件的写入之后以及关闭其句柄之前使用SetFileInformationByHandle()
来设置输出文件的最终大小。
例如:
type
FILE_INFO_BY_HANDLE_CLASS = (
FileBasicInfo = 0,
FileStandardInfo = 1,
FileNameInfo = 2,
FileRenameInfo = 3,
FileDispositionInfo = 4,
FileAllocationInfo = 5,
FileEndOfFileInfo = 6,
FileStreamInfo = 7,
FileCompressionInfo = 8,
FileAttributeTagInfo = 9,
FileIdBothDirectoryInfo = 10, // 0xA
FileIdBothDirectoryRestartInfo = 11, // 0xB
FileIoPriorityHintInfo = 12, // 0xC
FileRemoteProtocolInfo = 13, // 0xD
FileFullDirectoryInfo = 14, // 0xE
FileFullDirectoryRestartInfo = 15, // 0xF
FileStorageInfo = 16, // 0x10
FileAlignmentInfo = 17, // 0x11
FileIdInfo = 18, // 0x12
FileIdExtdDirectoryInfo = 19, // 0x13
FileIdExtdDirectoryRestartInfo = 20, // 0x14
MaximumFileInfoByHandlesClass);
FILE_END_OF_FILE_INFO = record
EndOfFile: LARGE_INTEGER;
end;
function SetFileInformationByHandle(
hFile: THandle;
FileInformationClass: FILE_INFO_BY_HANDLE_CLASS;
lpFileInformation: Pointer;
dwBufferSize: DWORD
): BOOL; stdcall; external 'kernel32.dll' delayed;
procedure MarcusCopyFileNoCache(SrcName, DestName: string);
const
BufSize = 10485760; {10MB}
var
Src, Dest: THandle;
Buffer: PByte;
FinalSize: Int64;
SectorSize, N, ignored: DWORD;
eof: FILE_END_OF_FILE_INFO;
begin
Src := CreateFile(PChar('\?'+SrcName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
if Src = INVALID_HANDLE_VALUE then RaiseLastOSError;
try
Dest := CreateFile(PChar('\?'+DestName), GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_FLAG_NO_BUFFERING, 0);
if Dest = INVALID_HANDLE_VALUE then RaiseLastOSError;
try
try
FinalSize := 0;
SectorSize := 512; // <-- TODO: determine this dynamically at runtime
GetMem(Buffer, BufSize);
try
repeat
if not ReadFile(Src, Buffer^, BufSize, N, nil) then RaiseLastOSError;
if N = 0 then Break; // EOF reached
Inc(FinalSize, N);
// round up the number of bytes read to the next sector boundary for writing
N := (N + (SectorSize-1)) and (not (SectorSize-1));
if not WriteFile(Dest, Buffer^, N, ignored, nil) then RaiseLastOSError;
until False;
finally
FreeMem(Buffer, BufSize);
end;
// now set the final file size
eof.EndOfFile.QuadPart := FinalSize;
if not SetFileInformationByHandle(Dest, FileEndOfFileInfo, @eof, SizeOf(eof)) then RaiseLastOSError;
finally
CloseHandle(Dest);
end;
except
DeleteFile(PChar(DestName));
raise;
end;
finally
CloseHandle(Src);
end;
end;
我想你知道答案了。您需要读取或写入扇区大小的倍数。这是既定的。
在读取时,我想这意味着对于最后一次读取,读取将成功,但您不会读取整个缓冲区。这将从lpNumberOfBytesRead
中显而易见。好的要求读取一个完整的缓冲区是没有问题的,但只读取文件中剩余的字节数。
因此,您不需要检查文件的大小,只需继续读取,直到读取不再返回任何字节。当我们讨论这个话题时,不要在这里使用浮点运算。使用整数运算符div
和mod
。
对于写入,您需要写入最后一块数据,四舍五入到扇区大小的倍数。这可能会写得太多。使用未缓冲的句柄调用SetEndOfFile
无法解决此问题。而是调用SetFileInformationByHandle
。传递FileEndOfFileInfo
并指定文件的真实长度。