2014-09-06 7 views
5

Das Problem ist, dass, wenn ich den Cache deaktivieren (Einstellung FILE_FLAG_NO_BUFFERING in CreateFile) muss ich an die Lese- und Schreibfunktion eine Anzahl von Bytes übergeben, die ein Vielfaches von 512 (Sektorgröße) ist. Ich benutze einen Puffer von 10 MB und alles ist in Ordnung ... bis zum letzten Pufferbetrieb. Der letzte Puffer hat kein Vielfaches von 512 Byte. Also, wie lese und schreibe ich diesen letzten Teil der Datei?Wie wird eine ungepufferte Dateiübertragung mit API ReadFile() WriteFile() durchgeführt?

Das ist, was ich bisher ...

schrieb
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; 
+1

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). –

+1

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. –

Antwort

5

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; 
+1

Das Lesen ist in Ordnung, aber wenn ich nur ganze Puffer schreibe, kann ich die Dateigröße nicht kontrollieren ... –

+0

Ja, Sie können. Wenn 'WriteFile()' es nicht erlaubt, einen partiellen Sektorpuffer am Ende der Datei zu schreiben, können Sie 'SetEndOfFile()' verwenden, um den EOF-Marker auf den gewünschten Dateioffset zu setzen. –

+1

Nein. Das funktioniert nicht mit einer ungepufferten Datei. Was ich in meiner Antwort behandle. –

4

Ich glaube, Sie kennen die Antwort. Sie müssen ein Vielfaches der Sektorgröße lesen oder schreiben. Das ist eine gegebene.

Beim Lesen, ich denke, dies bedeutet, dass für das letzte Lesen der Lesevorgang erfolgreich ist, aber Sie nicht den gesamten Puffer lesen. Das wird aus lpNumberOfBytesRead ersichtlich sein. Fein. Es ist kein Problem, einen vollständigen Puffer zu lesen, aber es werden nur so viele Bytes gelesen, wie in der Datei verblieben sind.

Sie müssen also nicht die Größe der Datei überprüfen, Sie können einfach weiterlesen, bis der Lesevorgang keine Bytes mehr zurückgibt. Und während wir zu diesem Thema sind, verwenden Sie hier keine Gleitkommaarithmetik. Verwenden Sie div und mod, ganzzahlige Operatoren.

Zum Schreiben müssen Sie den letzten Datenblock schreiben, aufgerundet auf ein Vielfaches der Sektorgröße. Dies wird möglicherweise zu viel schreiben. Sie können das nicht beheben, indem Sie SetEndOfFile mit Ihrem ungepufferten Handle aufrufen. Rufen Sie stattdessen SetFileInformationByHandle an. Übergeben Sie FileEndOfFileInfo und geben Sie die tatsächliche Länge der Datei an.

+0

'SetEndOfFile' setzt die Dateigröße auf die aktuelle Position des Dateizeigers. In einer Datei, die mit 'FILE_FLAG_NO_BUFFERING' geöffnet wurde, funktioniert' SetFilePointer' jedoch nur mit Werten, die ein Vielfaches der Sektorgröße sind. –

+0

Ich weiß. Daher was ich in meiner Antwort geschrieben habe. Bitte lesen Sie es noch einmal genauer. –

+1

Ich weiß nicht, ob dies mit einem Handle funktioniert, das 'FILE_FLAG_NO_BUFFERING' hat, aber eine andere Option könnte sein,' SetFileInformationByHandle() '] zu verwenden (http://msdn.microsoft.com/en-us/library/windows /desktop/aa365539.aspx), wobei der Parameter 'FileInformationClass' auf 'FileEndOfFileInfo' und der Parameter' lpFileInformation' auf einen Zeiger auf [FILE_END_OF_FILE_INFO'] gesetzt wurde (http://msdn.microsoft.com/de-de/) Bibliothek/Windows/Desktop/aa364224.aspx) Struktur, die den gewünschten EOF-Offset angibt. –

Verwandte Themen