Bei der Verwendung von FILE_FLAG_NO_BUFFERING
, Sie haben zu lesen und voll Sektoren zu schreiben, aber der letzte Puffer wird ein Teilsektor (ReadFile()
wird weniger Bytes berichten lesen als die gesamte Sektorgröße). Wenn Sie diesen letzten Puffer schreiben, müssen Sie ihn immer noch als einen vollständigen Sektor schreiben, sonst schlägt WriteFile()
fehl. Die Größe der Ausgabedatei ist also offensichtlich zu groß. Da Sie jedoch die gewünschte Dateigröße kennen, können Sie SetFileInformationByHandle()
verwenden, um die endgültige Größe der Ausgabedatei festzulegen, nachdem Sie mit dem Schreiben fertig sind und bevor Sie das Handle schließen.
Zum Beispiel:
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;
Ist garantiert, dass 'GetMem' einen Puffer mit Sektorgrößenanpassung zurückgibt? Ich glaube nicht, obwohl ich es nicht weiß. Siehe Dokumentation für 'ReadFile' und [' FileBuffering'] (http://msdn.microsoft.com/en-us/library/cc644950%28v=VS.85%29.aspx). –
Das glaube ich auch nicht, aber ich habe es getestet und es scheint, dass es sogar mit 'GetMem' funktioniert. Ich weiß nicht warum. Es ist komisch. Jedenfalls werde ich 'VirtualAlloc' in der endgültigen Version verwenden, um sicher zu sein. –