[ zurück ] Icon


Programmierung von Zip-Programmen mit den Delphi


Der eigene Zip-Viewer

Bereits mit dem Basiswissen über die Struktur von Zip-Files läßt sich ein voll funktionsfähiger Zip-Viewer entwickeln, der in eigene Programme integriert oder als eigenständiges kleines Tool gehandhabt werden kann. Dabei kommen zwei grundverschiedene Algorithmen in Frage.

Entweder durchsuchen Sie das ganze Zip-Archiv von Beginn an nach lokalen Dateiheadern so lange, bis der erste zentrale Dateiheader erscheint oder Sie durchsuchen nur die zentralen Dateiheader. Beide Vorgehensweisen führen Letztendes zum gleichen Ergebnis. Welche die schnellere ist, hängt vom Umfang des Archivs ab.

Im Folgenden wird die Variante mit Informationsgewinnung aus dem zentralen Dateiverzeichnis des Zip-Archivs beschrieben. Dieses befindet sich irgendwo gegen Ende der Zip-Datei nach der letzten komprimiert enthaltenen Inhaltsdatei. Wo genau, erfahren Sie aus dem Feld CentralOffSet aus dem Abschlußblock des Archivs (vgl. Tabelle 3 der Zip-Struktur).

Der Abschlußblock beginnt mindestens 22 Byte vor Archivende. Ein eventuell gespeicherter Kommentar verlagert den Beginn weiter nach vorn. Das Listing zeigt, wie Sie das Archiv vom Ende her nach dem Abschlußblock durchsuchen. Dabei werden höchstens 1 KByte Daten vom Ende her analysiert, ehe bei Mißerfolg abgebrochen wird.

 type DirRecord = packed record  // Ergebnis-Datensatz
   FileName       : String;      // Dateiname im Archiv
   PathName       : String;      // Pfadname
   DateTime       : Integer;     // (4) Datum und Uhrzeit
   CompresSize    : Cardinal;    // (4) gepackte Größe
   UncompresSize  : Cardinal;    // (4) ungepackte Größe
 end;

 type TZipList = array of DirRecord;   // Rückgabe-Array

 const LocalFileHeaderSig   = $04034B50;  // 'PK34'
       CentralFileHeaderSig = $02014B50;  // 'PK12'
       EndCentralDirSig     = $06054B50;  // 'PK56'
       ExtLocalSig          = $08074B50;  // 'PK78'

 function FillZipList(ZipName: String; var ZipList: TZipList): Integer;
          // Ermittelt die Kopfdaten aller im ZIP-Archiv gespeicherten Dateien 
 var ZipStream: TFileStream; ZipRecord: ZipDirEntry; EndRecord: ZipEndRecord;
     cBuf: array[0..255]of Char;
     nPos: LongInt; nSign, idx: Integer; nFlag: Byte;
     bEndFound: Boolean;
 begin
  SetLength(ZipList,0);                              // dynam. Array leeren
  if not FileExists(ZipName) then Exit;
  ZipStream:=TFileStream.Create(ZipName,fmOpenRead); // Archiv öffnen
  try
   if ZipStream.Size>22 then begin                   // EndeBlock ist 22 lang
    bEndFound:=false;
    nPos:=ZipStream.Seek(-20,soFromEnd);
    while (not bEndFound) and (nPos>0) and (nPos+1024>ZipStream.Size) do begin
     nPos:=ZipStream.Seek(-2,soFromCurrent);         // 2 zurück
     ZipStream.Read(nFlag,1);                        // 1 vor
     if nFlag=Ord('P') then begin                    // in 1er-Schritten suchen
      nPos:=ZipStream.Seek(-1,soFromCurrent);        // vor das 'P' zurück
      ZipStream.Read(nSign,4);
      if nSign=EndCentralDirSig then bEndFound:=true // Header-Flag gefunden
      else ZipStream.Seek(-4,soFromCurrent);
     end;
    end;                                             // durchsucht oder gefund

Ist die Kennung des EndCentralDirSig gefunden, wird der End-Record ausgelesen und das Datenfeld CentralOffSet zur Verschiebung des Streamzeigers auf den Ersteintrag im Zentralverzeichnis benutzt.

    if bEndFound then begin
     ZipStream.Read(EndRecord,SizeOf(EndRecord));    // 18-Byte-Struktur lesen
     ZipStream.Seek(EndRecord.CentralOffSet,soFromBeginning);

Ist dieser korrekt erreicht wird geprüft, ob tatsächlich auf einen zentralen Dateiheader positioniert wurde. Im Weiteren lesen Sie den folgenden Block aus 42 fixen Byte in einen Record ZipRecord der eigens deklarierten Struktur ZipDirEntry ein.

     idx:=0;                             // Anfang des Zentralheaders erreicht
     repeat
      ZipStream.Read(nSign,4);                       // Header-Flag lesen
      if nSign=CentralFileHeaderSig then begin       // zentr Dat-Header gefund
       ZipStream.Read(ZipRecord,SizeOf(ZipRecord));  // 42-Byte-Struktur

Der variabel lange Dateiname wird entsprechend seiner im Verzeichnisdatensatz enthaltenen Länge in einen separaten Puffer cBuf eingelesen und null-terminiert. Der Dateiname liegt danach leider OEM-codiert und nicht im Windows-typischen ANSI-Zeichensatz vor, wobei als Pfad-Trennzeichen des Unix-typische "/" statt des vertrauten "\" verwendet wird. Die Konvertierung und nachfolgende Aufspaltung in Pfad- und Dateinamen gestaltet sich aber unter Delphi mit den Funktionen OemToChar, ExtractFileName und ExtractFilePath unkompliziert.

       ZipStream.Read(cBuf,ZipRecord.FileNameLen);   // Dateinamen lesen
       cBuf[ZipRecord.FileNameLen]:=#0;              // nullterminieren
       OemToChar(cBuf,cBuf);                         // OEM --> ANSI
       while StrScan(cBuf,'/')<>nil do StrScan(cBuf,'/')^:='\';
       ZipStream.Seek(ZipRecord.FileCommentLen,soFromCurrent);

Anhand der Record-Daten werden nun die Dateiangaben zu Datum und Uhrzeit, komprimierter und unkomprimierter Größe der Originaldatei erfaßt. Gesammelt werden die Angaben zu den Inhaltsdateien eines solchen Zip-Archives im abgebildeten Listing in einem dynamischen Array.

       SetLength(ZipList,idx+1);                     // dynam. Array verlängern
       ZipList[idx].DateTime:=ZipRecord.DateTime;    // Ergebnisse einspeichern
       ZipList[idx].CompresSize:=ZipRecord.CompresSize;
       ZipList[idx].UncompresSize:=ZipRecord.UncompresSize;
       ZipList[idx].FileName:=ExtractFileName(cBuf);
       ZipList[idx].PathName:=ExtractFilePath(cBuf);
       Inc(idx);
      end;
     until nSign<>CentralFileHeaderSig;              // bis ungültiges Flag
     if ZipStream.Position=nPos+4 then FillZipList:=ZIP_NO_ERR;
    end;  // if bEndFound
   end;  // try
  finally
   ZipStream.Free;
  end;
 end;

Sollte Ihre Delphi-Version (vor Delphi 4) den Datentyp "dynamischse Array" noch nicht unterstützen, verwenden Sie statt dessen einfach eine Liste vom Typ TList zum Sammeln der Angaben über alle im Archiv enthaltenen Dateien. Das Download-Archiv "ZipView3.dpr" im Anhang beinhaltet die angepaßte Variante für die frühen Delphi-Versionen.

Der Einbau dieser Kernroutine FillZipList aus meiner Unit "ZipList.pas" ist bereits der halbe Zip-Viewer.

Das Übertragen der Daten aus der Sammelliste in ein Gitterelement TStringGrid einschließlich Formatierung ist im folgenden Listing angedeutet. Bei der Formatierung haben Sie natürlich die Möglichkeit, andere Wege zu gehen. Eventuell errechnen Sie auch noch in einer zusätzlichen Gitterspalte die Komprimierungsrate der Dateien als Prozentzahl. Diese Angabe ist nämlich im Zip-Archiv selbst nicht mit gespeichert.

 procedure TFrmMain.BtnViewClick(Sender: TObject);
 var DirList: TZipList;                       // dynamischs Array of DirRecord 
     i: Integer;
 begin
  Caption:='ZipViewer';
  if FillZipList(EdName.Text,DirList)=ZIP_NO_ERR then begin     // erfolgreich
   ZipGrid.RowCount:=Length(DirList)+1;                   // Gitter verlängern
   for i:=0 to High(DirList) do begin          // bis zum letzten Arrayelement
    ZipGrid.Cells[0,i+1]:=DirList[i].FileName;
    ZipGrid.Cells[1,i+1]:=
     FormatDateTime('dd.mm.yy hh:nn',FileDateToDateTime(DirList[i].DateTime));
    ZipGrid.Cells[2,i+1]:=FormatFloat('#00000000',DirList[i].UnCompresSize);
    ZipGrid.Cells[3,i+1]:=FormatFloat('#00000000',DirList[i].CompresSize);
    ZipGrid.Cells[4,i+1]:=DirList[i].PathName;
   end;
   ZipGrid.Cells[0,0]:='Dateinamen ('+IntToStr(Length(DirList))+')';
   Caption:='ZipViewer [ '+EdName.Text+' ]';
  end else begin                      // Archiv nicht erfolgreich durchforstet
   ZipGrid.RowCount:=2;                                       // Gitter leeren
   for i:=0 to 4 do ZipGrid.Cells[i,1]:='';
  end;
  DirList:=nil;                         // dynamisches Übergabearray freigeben
 end;

Die Hauptarbeit im Zip-Viewer leistet die Funktion FillZipList aus dem ersten Listing.

Den fertigen Zip-Viewer als eigenständiges Programm sehen Sie im folgenden Bild in Aktion. Interessenten finden im Quelltextarchiv des Programmes auch einen Abschnitt zur Verknüpfung des Dateityps "ZIP" mit dem Viewer über die Windows-Registry. Damit steht Ihnen der eigene Zip-Viewer dann auch im Kontextmenü des Windows-Explorers zur Verfügung.

Viewer

Der Viewer kann übrigens nicht nur echte Zip-Dateien, sondern wie abgebildet auch selbstextrahierende EXE-Datein (SFX-Archive) einsehen. Wenn Sie noch mehr wollen, nämlich Archive auch selbst packen und entpacken, dann sehen Sie auf die Seite Zip-Programme

J. Hummel,   Juni 2000

[ Programmierung ] [ Zip-Programmierung ]