如何使用API ReadFile()WriteFile()进行无缓冲文件传输



问题是,当我禁用缓存(在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中显而易见。好的要求读取一个完整的缓冲区是没有问题的,但只读取文件中剩余的字节数。

因此,您不需要检查文件的大小,只需继续读取,直到读取不再返回任何字节。当我们讨论这个话题时,不要在这里使用浮点运算。使用整数运算符divmod

对于写入,您需要写入最后一块数据,四舍五入到扇区大小的倍数。这可能会写得太多。使用未缓冲的句柄调用SetEndOfFile无法解决此问题。而是调用SetFileInformationByHandle。传递FileEndOfFileInfo并指定文件的真实长度。

最新更新