The problem is that when I disable the cache (setting FILE_FLAG_NO_BUFFERING
in CreateFile
) I must pass to the read and write function a number of bytes that is a multiple of 512 (sector size). I use a buffer of 10MB and everything it's ok... until the last buffer operation. The last buffer does not have a multiple of 512 number of bytes. So, how do I read and write this final portion of the file ?
This is what I wrote so far...
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;
writefile invokes the open, write, and close callable services to write or append text files, with lines up to 1024 characters long.
Reads data from the specified file or input/output (I/O) device. Reads occur at the position specified by the file pointer if supported by the device. This function is designed for both synchronous and asynchronous operations. For a similar function designed solely for asynchronous operation, see ReadFileEx.
When using FILE_FLAG_NO_BUFFERING
, you have to read and write full sectors, but the last buffer will be a partial sector (ReadFile()
will report fewer bytes read than the full sector size) . When you write that last buffer, you still have to write it as a full sector or else WriteFile()
will fail. So obviously the output file size may be too large. However, since you do know the desired file size, you can use SetFileInformationByHandle()
to set the output file's final size after you are done writing to it and before you close its handle.
For example:
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With