Delphi-PRAXiS
Seite 1 von 4  1 23     Letzte »    

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   FileSplitter: Probleme mit großen Dateien (https://www.delphipraxis.net/110825-filesplitter-probleme-mit-grossen-dateien.html)

Luckie 25. Mär 2008 13:52


FileSplitter: Probleme mit großen Dateien
 
Habe folgende Mail bezüglich großer Dateien und meines FileSplitters bekommen:
Zitat:

FileSplitter 5.7.0.3 (andere Versionen habe ich nicht getestet) versagt,
wenn die gesplitteten Dateien größer als 2047 MB sein sollen (getestet
unter XP SP2 mit NTFS). Ich wollte eine 6664,95 MB große Datei splitten
und erhielt dabei folgende Effekte:

Bei Angabe der Dateigröße zwischen 2048 und 4095 MB erscheint eine
10-stellige Anzahl Teile, und beim Versuch der Ausführung beendet sich
das Programm mit einem Runtime error.

Bei Angabe von 4096 MB als Dateigröße erhält man 0 Dateien und eine
Ausführung ist nicht möglich.

Bei Angabe von 4097 MB und mehr subtrahiert das Programm 4096 MB von der
Eingabe. Bei 4097 MB erhalte ich in meinem Beispiel also 6665 Teile zu 1 MB.

Bei Angabe von 2 bzw. 3 bei der Anzahl Dateien wird die korrekte
Dateigröße angezeigt und das Programm auch ausgeführt. Man erhält dann
allerdings 2 bzw 3 Dateien zu je 1 MB (und die Batch-Datei zum
Zusammenfügen).
Das wundert mich etwas, da ich eigentlich überall mit Int64 arbeite, wenn es um die Dateigrößen etc. geht. Hier mal die relevanten Codeauschnitte:

Delphi-Quellcode:
function SplitFile(Filename, DestFolder: string; SplitSize, CntParts: Int64): Integer;

  function GetClusterSize(Drive: Char): Cardinal;
  var
    SectorPerCluster: Cardinal;
    BytesPerSector : Cardinal;
    NumberOfFreeClusters: Cardinal;
    TotalNumberOfClusters: Cardinal;
  begin
    GetDiskFreeSpace(PChar(Drive + ':\'), SectorPerCluster, BytesPerSector, NumberOfFreeClusters,
      TotalNumberOfClusters);
    Result := SectorPerCluster * BytesPerSector;
  end;

var
  hFile            : THandle;
  SizeOfFile       : Int64;
  hPart            : THandle;
  Loop             : Cardinal;
  Partname         : string;
  BlockSize        : Cardinal;
  MemBuffer        : array of Byte;
  minlen           : Int64;
  BytesToRead, BytesRead, BytesWritten: Integer;
  OverallBytesRead : Int64;
  ProgressCurrent, ProgressOld: Int64;
begin
  TickStart := GetTickCount;

  BlockSize := -(-GetClusterSize(FileName[1]) and -GetClusterSize(DestFolder[1]) and -1048576);
  SetLength(MemBuffer, BlockSize - 1);
  bRunning := 1;
  OverallBytesRead := 0;
  SizeOfFile := GetFileSize(PChar(Filename));
  hFile := CreateFile(PChar(Filename), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL
    or FILE_FLAG_SEQUENTIAL_SCAN or FILE_FLAG_WRITE_THROUGH, 0);
  if hFile <> INVALID_HANDLE_VALUE then
  begin
    for Loop := 1 to CntParts do
    begin
      // Reset variables
      ProgressOld := 0;
      BytesToRead := SplitSize;
      // build filename of the parts
      Partname := DestFolder + '\' + ExtractFilename(Filename) + Format('.%3.3d', [Loop]);
      if FileExists(Partname) then
        DeleteFile(PChar(Partname));
      hPart := CreateFile(PChar(Partname), GENERIC_WRITE, FILE_SHARE_WRITE, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL
        or FILE_FLAG_SEQUENTIAL_SCAN or FILE_FLAG_WRITE_THROUGH, 0);
      if hPart <> INVALID_HANDLE_VALUE then
      begin
        repeat
          minlen := Min(length(MemBuffer), BytesToRead);
          BytesRead := FileRead(hFile, MemBuffer[0], minLen);
          if BytesRead > -1 then
          begin
            BytesWritten := FileWrite(hPart, MemBuffer[0], BytesRead);
            Dec(BytesToRead, length(MemBuffer));
            // progress stuff ////////////////////////////////////////////////////
            OverallBytesRead := OverallBytesRead + BytesWritten;
            ProgressCurrent := (OverallBytesRead * 100) div SizeOfFile;
            if ProgressCurrent <> ProgressOld then
            begin
              ProgressOld := ProgressCurrent;
            end;
            SendMessage(hApp, FSM_PROGRESS, ProgressCurrent, Integer(PChar(Partname)));
          end
          else
          begin
            MessageBox(hApp, PChar(SysErrorMessage(GetLastError)), PChar(APPNAME), 0);
            Break;
          end;
          //////////////////////////////////////////////////////////////////////
        until (BytesToRead <= 0) or (bRunning = 0);
      end;
      FileClose(hPart);
      if bRunning = 0 then
        Break;
    end;
    FileClose(hFile);
  end;
  SendMessage(hApp, FSM_FINISH, GetTickCount - TickStart, GetLastError());
  result := GetLastError();
end;
Delphi-Quellcode:
function CalcCntParts(const Filename: string; Size: Int64): Cardinal;
var
  FileSize         : Int64; // >4GB
begin
  result := 0;
  if Size > 0 then
  begin
    FileSize := GetFileSize(PChar(Filename));
    if (FileSize > 0) and (FileSize div Size < High(Integer)) then
      result := (FileSize - 1) div Integer(Size) + 1;
  end;
end;

function CalcFileSize(const Filename: string; CntParts: Cardinal): Int64;
var
  FileSize         : Int64;
begin
  Result := 0;
  FileSize := GetFileSize(PChar(Filename));
  if (FileSize > 0) and (CntParts <> 0) then
  begin
    Result := (FileSize div CntParts) + 1;
  end;
end;
Delphi-Quellcode:
////////////////////////////////////////////////////////////////////////////////
// Procedure : FileRead
// Comment  : Reads the given amount of bytes into a buffer
function FileRead(Handle: Integer; var Buffer; Count: LongWord): Integer;
begin
  if not ReadFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then
    Result := -1;
end;

////////////////////////////////////////////////////////////////////////////////
// Procedure : FileWrite
// Comment  : Writes the buffer into the file
function FileWrite(Handle: Integer; const Buffer; Count: LongWord): Integer;
begin
  if not WriteFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then
    Result := -1;
end;
Ich weiß nicht, wo ich den Fehlergemacht haben soll.

MrKnogge 25. Mär 2008 13:57

Re: FileSplitter: Probleme mit großen Dateien
 
Moin Michael,

hast du mal eine dummy-Datei in der erwähnten Größe erstellt und deinen Filesplitter damit durch den debugger gejagt ?

Luckie 25. Mär 2008 14:47

Re: FileSplitter: Probleme mit großen Dateien
 
Das ist das Problem. Ich habe keine Partition mehr übrig mit genügend Platz. ;)

shmia 25. Mär 2008 14:54

Re: FileSplitter: Probleme mit großen Dateien
 
Ich verstehe nicht, weshalb der Parameter CntParts überhaupt übergeben wird.
Angenommen, ich möchte eine Datei mit 5 GB in 650 MB grosse Stücke unterteilen.
Dann gehe ich doch so vor (Pseudocode):
Delphi-Quellcode:
Datei öffnen
while not eof do
begin
  ErzeugeNeueZieldatei;
 
  readsize := 0
  do
    LesePuffer; // 8 kB lesen
    SchreibeZieldatei; // bis zu 8 kB schreiben
    if readBytes <> Buffersize then break;
    readsize := readsize + readBytes;
  until readsize >= 650 MB;
  SchlieseZielDatei;
end;
Auf diese Art braucht man keine For-Schleife und entgeht den ganzen Problemen mit Int64 Werten.
Kleiner Nachteil meiner Lösung: die Grösse der Zieldatei muss ein Vielfaches der Puffergrösse sein.
Mit etwas Zusatzaufwand kann man das aber auch lösen.

Luckie 25. Mär 2008 15:09

Re: FileSplitter: Probleme mit großen Dateien
 
Zitat:

Zitat von shmia
Auf diese Art braucht man keine For-Schleife und entgeht den ganzen Problemen mit Int64 Werten.

Ich glaube, bei der for-Schleife dürfte es keine Problme mit dem Wertebereich geben. Wer will eine Datei schon in 2^32 Teile teilen. ;)

shmia 25. Mär 2008 15:27

Re: FileSplitter: Probleme mit großen Dateien
 
Zitat:

Zitat von Luckie
Zitat:

Zitat von shmia
Auf diese Art braucht man keine For-Schleife und entgeht den ganzen Problemen mit Int64 Werten.

Ich glaube, bei der for-Schleife dürfte es keine Problme mit dem Wertebereich geben. Wer will eine Datei schon in 2^32 Teile teilen. ;)

Ich würde trotzdem keine for-schleife verwenden; der Parameter ist doch überflüssig.
Wir kennen die Dateigrösse und die Grösse der Teildateien. Bringt man jetzt noch die Anzahl der Teildateien ins Spiel,
ist das wie eine überbestimmte Gleichung.

Delphi-Quellcode:
SizeOfFile := GetFileSize(PChar(Filename));
Bist du sicher, dass du die richtige Funktion verwendest ?
Unter Delphi 5 sieht der Prototyp aus Windows.pas so aus:
Delphi-Quellcode:
function GetFileSize(hFile: THandle; lpFileSizeHigh: Pointer): DWORD; stdcall;
Bist du sicher, dass deine Funktion korrekt arbeitet (also wirklich int64 liefert) ?

Luckie 25. Mär 2008 15:47

Re: FileSplitter: Probleme mit großen Dateien
 
Sieht bei mir so aus:
Delphi-Quellcode:
function GetFileSize(Filename: String): Int64;
var
  fFile            : THandle;
  wfd              : TWIN32FINDDATA;
begin
  result := -1;
  if not FileExists(Filename) then
    exit;
  fFile := FindFirstfile(pchar(Filename), wfd);
  if fFile = INVALID_HANDLE_VALUE then
    exit;
  result := (wfd.nFileSizeHigh * (Int64(MAXDWORD) + 1)) + wfd.nFileSizeLow;
  windows.FindClose(fFile);
end;
Das mit der for-Schleife wäre zu überlegen. Wenn Teile angegeben werden, müsste man eben erst die Größe der Teilstücke ausrechnen. Ich werde mir das ml überlegen.

shmia 25. Mär 2008 16:14

Re: FileSplitter: Probleme mit großen Dateien
 
Zitat:

Zitat von Luckie
Sieht bei mir so aus:
Delphi-Quellcode:
function GetFileSize(Filename: String): Int64;
var
  fFile            : THandle;
  wfd              : TWIN32FINDDATA;
begin
  result := -1;
  if not FileExists(Filename) then
    exit;
  fFile := FindFirstfile(pchar(Filename), wfd);
  if fFile = INVALID_HANDLE_VALUE then
    exit;
  result := (wfd.nFileSizeHigh * (Int64(MAXDWORD) + 1)) + wfd.nFileSizeLow;
  windows.FindClose(fFile);
end;

Au, wo hast du denn diese Funktion her ?
Das mit dem Multiplizieren ((wfd.nFileSizeHigh * (Int64(MAXDWORD) + 1)) löst bei mir "schlechte Gefühle" aus.
Ich glaube die VCL bzw. der Compiler kann gar keine Multiplikationen der Bauart
64bit * 32Bit = 64bit
ausführen.
Delphi-Quellcode:
function FileGetSize(const FileName: string): Int64; // kopiert aus der JCL
var
  SearchRec: TSearchRec;
  OldMode: Cardinal;
  Size: TULargeInteger;
begin
  Result := -1;
  OldMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  try
    if FindFirst(FileName, faAnyFile, SearchRec) = 0 then
    begin
      Size.LowPart := SearchRec.FindData.nFileSizeLow;
      Size.HighPart := SearchRec.FindData.nFileSizeHigh;
      Result := Size.QuadPart;
      SysUtils.FindClose(SearchRec);
    end;
  finally
    SetErrorMode(OldMode);
  end;
end;

marabu 25. Mär 2008 18:18

Re: FileSplitter: Probleme mit großen Dateien
 
Hallo Andreas,

Zitat:

Zitat von shmia
... Ich glaube die VCL bzw. der Compiler kann gar keine Multiplikationen der Bauart 64bit * 32Bit = 64bit ausführen. ...

du meinst den Compiler - und deiner kann sowas. Die Redefinition eines QuadWord anstatt einer Multiplikation ist aber der bessere Weg.

Freundliche Grüße

Luckie 25. Mär 2008 21:05

Re: FileSplitter: Probleme mit großen Dateien
 
Liste der Anhänge anzeigen (Anzahl: 1)
So, ich habe mal die vorgeschlagenen Änderungen gemacht. Anbei die aktuelle Exe.

Hätte jemand von euch genug Festplattenkapazität, um das bitte mal mit Dateien größer 6,6 GB und Teilstücken größer 2048 MB zu testen, eben wie inder Fehlermeldung im ersten Posting? Wäre sehr nett. Allerdings glaube ich nicht, dass die Änderungen etwas mit dem Fehler zu tun haben.

Mir fällt gerade ein, kann es sein das er versucht hat die Teilstücke auf einer FAT32 Partition zu erstellen? Die kann doch nur Dateien bis 2GB verwalten oder?


Alle Zeitangaben in WEZ +1. Es ist jetzt 09:44 Uhr.
Seite 1 von 4  1 23     Letzte »    

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz