Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2014年10月28日] [更新 2015年04月07日] Delphi サンプルプログラム集
   
744_JEDI プロジェクトを使用したファイルの圧縮・解凍
動作確認等 Windows 7 U64(SP2) + Delphi XE(UP1) Pro + JCL 2.7.0.5300
744_JEDIArchiver.zip [4,282 KB] 2015年04月07日版 (EXE 同梱)



  • 2015年03月26日
  • 進捗状況を表示するコードを変更 (プログレスバー)
    JEDI に添付のサンプルを真似ていたのであるが,処理方法が明確でないので
  • .tar.gz 等の書庫作成と展開を可能にするために,書庫作成と展開のルーチンを別にした
  • 2015年04月07日
  • ZLIB のサンプルを追加.ただし,Delphi に実装されている Zlib ユニットを使用




JEDI プロジェクトを使用したファイルの圧縮・解凍


JEDI プロジェクトの JCL のアーカイブ関係のライブラリを使用したサンプルです.JCL は,JEDI プロジェクトの非ビジュアルコンポーネント関係のライブラリです.
TJclCompressionArchive という基本クラスとその派生クラスを利用しています.派生クラス等についてはソースコード等で確認してください.

JEDI のアーカイブの操作関係の関数類は,主に 7z.dll を使用して,その機能を使用しています.
7z.dll は 7-Zip という圧縮・解凍ツールで使用されている DLL です.これは,総合アーカイバプロジェクトで提供されている DLL とは違います.
添付の 7z.dll を,EXE のあるフォルダに置いてテストしてください.DLL は,JEDI プロジェクトのページからもダウンロードできます.

今回使用した JEDI JCL, JEDI JVCL は以下の GitHubからダウンロードしています.
JEDI JVCL を使用するには JEDI JCL のインストールも必要です.3 番目のリンクは DLL 関係のダウンロードのリンクです.最後のリンクは 7-Zip 本家のサイトです.7z.dll はこちらでもダウンロードできます.



以下は,JEDI プロジェクトのサイトへのリンクと関係記事へのリンクです.



[備考]
2015年03月26日
--- JEDI コードライブラリとコンポーネントライブラリ (JCL/JVCL) のインストール ---

今回の更新では [JCL: JEDI Code Library 2.7.0.5300, JVCL: JEDI Visual Component Library 3.48] をインストールしてサンプルプログラムをテストしています.ダウンロードした JCL に添付されている readme.md にも書いてありますが,インストールには jedi.inc というファイルが必要です.このファイルを以下からダウンロードして,[...\jcl\jcl\source\include\jedi] フォルダ内に置きます.
具体的なインストール 方法は,上の参考リンクの記事で確認してください.



下図は,JEDI プロジェクトに添付されているサンプルプロジェクトを,Delphi XE(UP1) Pro でコンパイルして実行した画面です.



  • 図1
  • [examples] [windows] [compression] [archive] のプロジェクトををコンパイルして実行

当サイトには,この他,以下のようなアーカイブファイルの圧縮・解凍関係のサンプルがあります.これらの記事中のリンクも参考にしてください.





01_JEDI プロジェクトを使用したファイルの圧縮・書庫作成


拡張子が .zip, .7z その他,いくつかの書庫ファイルを作成するサンプルです.
これ以外の書庫形式については,各自で確認してください.
フォルダの選択ダイアログで選択したフォルダ内のファイル全て含む書庫を作成,または,ファイルの選択ダイアログで選択した単一のファイルを圧縮します.書庫名は元のフォルダ名に拡張子を付加したものにしています.単一ファイルの場合は,元のファイル名に拡張子を付加します.



図2
設計時画面
  • 書庫の作成には 7z.dll が必要
  • 書庫によっては bzip2.dll, zlib1.dll も必要な場合がある
図3
実行中の画面
  • 書庫が作成できても,その書庫特有の情報が書かれたファイル等がないと,本来の書庫として機能しない形式もあるので注意
  • GZ, GZIP, BZ2, BZIP2 は単一ファイルの圧縮用
  • GZ, GZIP は同じ形式の書庫.拡張子が違うだけ
  • BZ2, BZIP2 は同じ形式の書庫.拡張子が違うだけ
  • .TAR.GZ と .TGZ も同じ形式の書庫

リスト1
JEDI プロジェクトを使用したファイルの圧縮・書庫作成
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls, JclCompression, Contnrs;

const
  WM_SYNC_ARCHIVE_PROGRESS = WM_USER + 153;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ProgressBar1: TProgressBar;
    RadioGroup1: TRadioGroup;
    RadioGroup2: TRadioGroup;
    Button2: TButton;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    function CompressSaveArchive(SourcePath, ArchiveName, Password: String;
      ProgressEvent: TJclCompressionProgressEvent=nil): Integer;
    procedure Archive_AddFiles(Archive: TJclCompressArchive;
      BaseDir, ADir: String; var ASumSize: Int64);
    procedure WmSyncArchiveProgress(var Message: TMessage);
      message WM_SYNC_ARCHIVE_PROGRESS;
  public
    { Public 宣言 }
    procedure ArchiveProgress(Sender: TObject; const Value, MaxValue: Int64);
  end;

var
  Form1: TForm1;

implementation

uses
  FileCtrl, IOUtils, StrUtils;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  RadioGroup1.ItemIndex := 0;
  RadioGroup2.ItemIndex := 0;
end;

//=============================================================================
//  圧縮(書庫作成)
//  フォルダ内の全てのファイルを格納した書庫を作成
//  書庫の作成先は,プロジェクトフォルダ内の[Temp]フォルダ内
//  書庫ファイル名は元のフォルダ名に書庫の拡張子を付加
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LArchiveFileName : String;
  LSoureDir        : String;
  LOutBaseDir      : String;
  LResult          : Integer;
begin
  ProgressBar1.Position := 0;
  ProgressBar1.Max      := 1000;

  LSoureDir := ExtractFileDir(Application.ExeName);
  if not SelectDirectory('フォルダ選択', '', LSoureDir, [sdNewUI], Self) then exit;

  //作成する書庫名のフルパス
  //拡張子を変更するとその拡張子の書庫を作成する
  LOutBaseDir := ExtractFilePath(Application.ExeName) + 'Temp';
  LArchiveFileName := IncludeTrailingPathDelimiter(LOutBaseDir)
                    + ExtractFileName(LSoureDir)
                    + Trim(RadioGroup1.Items[RadioGroup1.ItemIndex]);

  //書庫作成用のルーチンを実行
  LResult :=  CompressSaveArchive(LSoureDir,
                                  LArchiveFileName,
                                  '',
                                  ArchiveProgress);

  if LResult < 0 then begin
    MessageBox(Handle, '未サポート書庫形式', '結果', MB_ICONWARNING);
  end else
  if LResult > 0 then begin
    MessageBox(Handle, '書庫作成失敗', '結果', MB_ICONSTOP);
  end;
end;

//=============================================================================
//  圧縮(書庫作成)
//  1つのファイルだけを圧縮(.gz等)
//  書庫の作成先は,プロジェクトフォルダ内の[Temp]フォルダ内
//  書庫ファイル名は元のフォルダ名に書庫の拡張子を付加
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LArchiveFileName : String;
  LSouceFileName   : String;
  LOutBaseDir      : String;
  LResult          : Integer;
begin
  if not OpenDialog1.Execute then exit;

  ProgressBar1.Position := 0;
  ProgressBar1.Max      := 1000;

  //展開対象のアーカイブ名
  LSouceFileName := OpenDialog1.FileName;

  //作成する書庫名のフルパス
  //拡張子を変更するとその拡張子の書庫を作成する
  LOutBaseDir := ExtractFilePath(Application.ExeName) + 'Temp';
  LArchiveFileName := IncludeTrailingPathDelimiter(LOutBaseDir)
                    + ExtractFileName(LSouceFileName)
                    + Trim(RadioGroup2.Items[RadioGroup2.ItemIndex]);

  //書庫作成用のルーチンを実行
  LResult :=  CompressSaveArchive(LSouceFileName,
                                  LArchiveFileName,
                                  '',
                                  ArchiveProgress);

  if LResult < 0 then begin
    MessageBox(Handle, '未サポート書庫形式', '結果', MB_ICONWARNING);
  end else
  if LResult > 0 then begin
    MessageBox(Handle, '書庫作成失敗', '結果', MB_ICONSTOP);
  end;
end;

//-----------------------------------------------------------------------------
//  書庫作成・解凍用のルーチン
//
//  SourcePath    : フォルダ内のファイルが対象の場合はそのフォルダのフルパス
//                  デリミタの有無は問わない
//                  単一のファイルの圧縮の場合はそのファイルのフルパス
//  ArchiveName   : 作成する書庫のフルパス(つまり保存名)
//  Password      : パスワード
//  ProgressEvent : TJclCompressionArchive.OnProgressイベント用のメソッド名
//                  イベントを使用しない場合はnilにする
//
//  戻り値
//   0  : 成功(多分)
//  -1  : サポートしていない書庫形式
//   1  : 操作失敗
//-----------------------------------------------------------------------------
function TForm1.CompressSaveArchive(SourcePath, ArchiveName, Password: String;
  ProgressEvent: TJclCompressionProgressEvent): Integer;
var
  LSavePath       : String;
  LSourcePath     : String;
  LSingleFilePath : String;
  LFileExt        : String;
  LFileExtTar     : String;
  LPassword       : String;
  LRetFormat      : Boolean;
  LFormat         : TJclUpdateArchiveClass;
  LArchive        : TJclCompressionArchive;
  LCompArchive    : TJclCompressArchive;
  LTotalFileSize  : Int64;
begin
  Result := 0;

  LPassword       := Password;
  LSavePath       := ArchiveName;
  LFileExt        := ExtractFileExt(LSavePath);
  LFileExtTar     := '';
  LSingleFilePath := '';

  //ディレクトリが存在すればディレクトリ内のファイルの書庫作成と見なす
  //そうでない場合は単一のファイルの圧縮と見なす
  if SysUtils.DirectoryExists(SourcePath) then begin
    LSourcePath := IncludeTrailingPathDelimiter(SourcePath);
  end else begin
    LSingleFilePath := SourcePath;
  end;

  //.tar.gz, tgz等かを調べる
  //LFileExtTarが空文字のままなら.tar.gz, tgz等ではない
  LFileExtTar := ExtractFileExt(ExtractFileName(ChangeFileExt(LSavePath, '')));
  if UpperCase(LFileExtTar) = '.TAR' then begin
    LSavePath   := ChangeFileExt(ChangeFileExt(LSavePath, ''), LFileExtTar);
    LFileExtTar := LFileExtTar + LFileExt;
  end else
  if Pos('.T', UpperCase(LFileExt)) > 0 then begin
    if Pos('Z', UpperCase(LFileExt)) > 0 then begin
      LFileExtTar := LFileExt;
      LFileExt    := StringReplace(LFileExt, '.T', '.', [rfIgnoreCase]);
      LSavePath   := ChangeFileExt(LSavePath, '.tar');
    end;
  end else begin
    //tar関係の書庫作成ではない場合
    LFileExtTar := '';
  end;


  //作成した書庫ファイルの保存先のフォルダを作成
  SysUtils.ForceDirectories(ExtractFileDir(LSavePath));

  //.tar.xx, txx形式の場合は.tar書庫を作成してそこにファイルを格納
  //本ルーチン自身を呼び出して処理
  if LFileExtTar <> '' then begin
    Result := CompressSaveArchive(LSourcePath, LSavePath, LPassword, ProgressEvent);
    LSingleFilePath := LSavePath;
    LSavePath       := ChangeFileExt(LSavePath, '') + LFileExt;
  end;


  if Result = 0 then begin
    Result := -1;

    //拡張子に応じたフォーマットオブジェクトを検索
    LFormat := GetArchiveFormats.FindUpdateFormat(LSavePath);
    LRetFormat := False;
    if LFormat <> nil then begin

      //アーカイバのインスタンスを生成
      LArchive            := LFormat.Create(LSavePath);
      LArchive.Password   := LPassword;
      LArchive.OnProgress := ProgressEvent;

      try
        //書庫作成用のオブジェクト
        LCompArchive  := LArchive as TJclCompressArchive;

        //単一のファイルはTJclCompressArchive.AddFileでそのファイルを書庫に追加
        //指定フォルダ内のファイルの場合(複数のファイル)は専用のメソッドで処理
        if LSingleFilePath <> '' then begin
          if Assigned(ProgressEvent) then begin
            LCompArchive.OnProgress(nil, 0, 0);
          end;
          LCompArchive.AddFile(ExtractFileName(LSingleFilePath), LSingleFilePath);
        end else begin
          LTotalFileSize := 0;
          Archive_AddFiles(LCompArchive, LSourcePath, LSourcePath, LTotalFileSize);
        end;
        LRetFormat := True;
      except
        FreeAndNil(LArchive);
      end;
    end;


    if LRetFormat then begin
      try
        try
          Screen.Cursor := crHourGlass;
          //実際の書庫の作成
          (LArchive as TJclCompressArchive).Compress;
          if LFileExtTar <> '' then begin
            RenameFile(LSavePath, ChangeFileExt(LSavePath, LFileExtTar));
          end;
          Result := 0;
        except
          //書庫作成失敗
          Result := 1;
        end;
      finally
        Screen.Cursor := crDefault;
        FreeAndNil(LArchive);
      end;
    end;
  end;

  //.tar.xx形式で生成したファイルは削除
  if LFileExtTar <> '' then begin
    if FileExists(LSingleFilePath) then begin
      DeleteFile(LSingleFilePath);
    end;
    if FileExists(LSavePath) then begin
      DeleteFile(LSavePath);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  指定ディレクトリ内のファイルを書庫に追加するルーチン
//  ついでにファイルサイズの合計を計算して返す
//
//  Archive      : TJclCompressArchiveのインスタンス;
//  BaseDir      : 基準ディレクトリ.書庫に格納するファイルがあるディレクトリ
//                 デリミタの有無は問わない
//  ADir         : 同上.こちらの値は再帰処理によって変化する
//  var ASumSize : 書庫に格納するファイルのサイズの合計
//                 呼び出し側で0にしておく
//
//  [備考]
//  TJclCompressArchive.AddDirectoryというメソッドがあるが,エラーなしで終了し
//  ても,書庫の展開ができなかったりするので,このルーチンで,ファイルを1つづ
//  つ処理することにした
//-----------------------------------------------------------------------------
procedure TForm1.Archive_AddFiles(Archive: TJclCompressArchive;
  BaseDir, ADir: String; var ASumSize: Int64);
var
  LSearchPath : string;
  LSearchRec  : TSearchRec;
  LFullPath   : String;
  LFilePath   : String;
begin
  LSearchPath := IncludeTrailingPathDelimiter(ADir) + '*.*';
  if FindFirst(LSearchPath, faAnyFile, LSearchRec) = 0 then
  begin
    repeat
      if not ((LSearchRec.Name = '..') or (LSearchRec.Name = '.')) then begin
        if (LSearchRec.Attr and faDirectory) = faDirectory then begin
          LFullPath := IncludeTrailingPathDelimiter(ADir) + LSearchRec.Name;
          //パス名を与えて自分自身を呼出(再帰処理)する
          Archive_AddFiles(Archive, BaseDir, LFullPath, ASumSize);
        end else begin
          LFilePath := LSearchRec.Name;
          if Trim(LSearchRec.Name) <> '' then begin
            LFullPath := IncludeTrailingPathDelimiter(ADir) + LSearchRec.Name;
            LFilePath := StringReplace(LFullPath, BaseDir, '', [rfIgnoreCase]);

            //合計ファイルサイズを加算
            ASumSize := ASumSize + LSearchRec.Size;
            //書庫にファイルを追加する
            Archive.AddFile(LFilePath, BaseDir + LFilePath);
          end;
        end;
      end;
    until FindNext(LSearchRec) <> 0;
    FindClose(LSearchRec);
  end;
end;

//-----------------------------------------------------------------------------
//  TJclCompressArchiveのOnProgressイベント処理
//  本イベントは,Compressメソッドが実行されてから発生するように思われる
//  いろいろ工夫が必要かもしれない
//  MaxValueの値は,実際のファイルサイズよりも大きい値となっているようである
//  それが何の値かは不明
//-----------------------------------------------------------------------------
procedure TForm1.ArchiveProgress(Sender: TObject; const Value, MaxValue: Int64);
begin
  //PostMessageでメッセージを送信して処理
  PostMessage(Handle, WM_SYNC_ARCHIVE_PROGRESS, Value, MaxValue);
end;

//-----------------------------------------------------------------------------
//  TJclCompressArchiveのOnProgressイベントからのメッセージ処理
//-----------------------------------------------------------------------------
procedure TForm1.WmSyncArchiveProgress(var Message: TMessage);
var
  LValue : Int64;
  MaxValue : Int64;
begin
  LValue   := Message.WParam;
  MaxValue := Message.LParam;
  if MaxValue > 0 then begin
    ProgressBar1.Position := Round(LValue / MaxValue * 1000);
  end;
end;

end.


上のコードでは,書庫にファイルを追加する部分を別の関数にしています.
当初,格納するファイルの合計サイズを計算するためにそうしたのですが,実際にはファイルの合計サイズは使用していません.したがって,Delphi 2010 以降では以下のように書くことができます.非常に簡略化することができます.別の関数にするまでもないでしょう.
LFileName, LSaveFileName は String 型の変数です.



          for LFileName in TDirectory.GetFiles(
            LSourcePath, '*.*', TSearchOption.soAllDirectories) do begin

            LSaveFileName := StringReplace(LFileName, LSourcePath, '', [rfIgnoreCase]);
            LCompArchive.AddFile(LSaveFileName, LFileName);
          end;




02_JEDI プロジェクトを使用したファイルの解凍・書庫展開


選択した書庫ファイルを展開するサンプルです.これも,展開先を EXE のあるフォルダ内の [Temp] フォルダに固定しています.ISO, MSI, CAB, WIM 形式のファイルも展開できます.

JEDI プロジェクトの該当クラスでサポートしている書庫形式に .tgz, .tpz, .tbz2, .tbz がありますが,これらは,圧縮を解凍して .tar ファイルを生成するだけです.そこで,本サンプルでは,解凍・展開のメソッドを再帰的に実行することで,これらのファイルの解凍・展開をしています.
前項のサンプルでは逆に,アーカイブの処理を再帰的に実行することで,それらのファイルを作成するようにしています.



図4
設計時画面
  • 書庫の解凍・展開には 7z.dll が必要
  • 書庫によっては bzip2.dll, zlib1.dll も必要な場合がある

下図はエクセルの xlsx 形式のブックを展開した結果の例です.画像があれば,その展開したファイルから取り出すこともできます.Office 2007 以降の .xlsx, docx ファイル等は zip 形式の書庫です.拡張子を .zip に変更すれば,zip 書庫を展開できるプログラムであれば展開できます.


図5
エクセルの .xlsx 形式ブックの展開結果

下図は test.exe という EXE ファイルを展開した結果です.
Test 直下には EXE の各セッションのデータがありますが,もちろんバイナリデータです.
MANIFEST フォルダにはいわゆるアプリケーションマニフェストファイルが格納されています.これはテキストファイルです.


図6
EXE ファイルの展開結果


リスト2
JEDI プロジェクトを使用したファイルの解凍・展開
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls, JclCompression, Contnrs;

const
  WM_SYNC_ARCHIVE_PROGRESS = WM_USER + 153;

type
  TForm1 = class(TForm)
    ProgressBar1: TProgressBar;
    OpenDialog1: TOpenDialog;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    FTotalFileSize : Int64;
    function ExtractArchive(ArchiveName, DestDir, Password: String;
      ProgressEvent: TJclCompressionProgressEvent=nil): Integer;
    procedure WmSyncArchiveProgress(var Message: TMessage);
      message WM_SYNC_ARCHIVE_PROGRESS;
  public
    procedure ArchiveProgress(Sender: TObject; const Value, MaxValue: Int64);
  end;

var
  Form1: TForm1;

implementation

uses IOUtils, StrUtils;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//
//  使用可能な拡張子をセット
//  ただし,このリストは単純にGetArchiveFormatsから取得したもの
//  全てが使用可能かどうかは不明
//  書庫に種類によってはEXEのフォルダ内に該当DLLが必要な場合がある
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  SL : TStringList;
  i  : Integer;
begin
  SL := TStringList.Create;
  try
    SL.Add('|*.lzh;*.zip;*.7z|');
    SL.Add('|*.iso;*.msi;*.cab;*.exe;*.dll|');
    SL.Add('|*.docx;*.xlsx;*.pptx;*.doc;*.xls;*.ppt|');
    SL.Add('|*.tar|');
    SL.Add('|*.gz;*.gzip;*.tgz;*.tpz|');
    SL.Add('|*.bz2;*.bzip2;*.tbz2;*.tbz|');
    SL.Add('|*.pcv;*.bsz;*.mskin;*.wmz;*.ipa;*.sxw;*.sxi|');
    SL.Add('|*.xz;*.txz|');
    SL.Add('|*.swf|');
    SL.Add('|*.wim;*.swm|');
    SL.Add('|*.sxt;*.sxd;*.sxc;*.sxm;*.sxg;*.stw;*.sti;*.std;*.stc;*.odh;*.odd|');
    SL.Add('|*.odt;*.odm;*.ods;*.ots;*.odg;*.otg;*.odp;*.otp;*.odf;*.odb|');
    SL.Add('|*.jar;*.ear;*.war;*.cbz;*.apk;*.wsz;*.wal;*.xpi;*.crx;*.dfsz|');

    for i := 0 to SL.Count -1 do begin
      OpenDialog1.Filter := OpenDialog1.Filter +  '' + SL[i];
    end;
  finally
    FreeAndNil(SL);
  end;
end;

//=============================================================================
//  書庫の解凍(展開)
//  展開先はプロジェクトフォルダ下の[Temp]フォルダ内

//  [Temp]フォルダ内にファイル名から拡張子を除いたフォルダを作成して格納
//  出力先のディレクトリがない場合は,自動で作成する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBaseDir         : String;
  LArchiveFileName : string;
  LDestDirectory   : string;
  LResult          : Integer;
begin
  if not OpenDialog1.Execute then exit;

  ProgressBar1.Position := 0;
  ProgressBar1.Max      := 1000;

  //展開対象のアーカイブ名
  LArchiveFileName := OpenDialog1.FileName;

  LBaseDir := ExtractFilePath(Application.ExeName) + 'Temp';

  //解凍(展開)先のフォルダ
  LDestDirectory := IncludeTrailingPathDelimiter(LBaseDir)
                  + ExtractFileName(ChangeFileExt(LArchiveFileName, '')) ;

  //筆者がよく使用する出力先の指定の場合
  //アーカイブがあるフォルダ内にフォルダを作成して,そこに解凍する場合
  //LDestDirectory := ExtractFilePath(Application.ExeName)
  //                + ExtractFileName(ChangeFileExt(LArchiveFileName, '')) ;

  LResult := ExtractArchive(LArchiveFileName, LDestDirectory, ' ', ArchiveProgress);

  if LResult < 0 then begin
    MessageBox(Handle, '未サポート書庫形式', '結果', MB_ICONWARNING);
  end else
  if LResult > 0 then begin
    MessageBox(Handle, '書庫展開 (解凍) 失敗', '結果', MB_ICONSTOP);
  end;
end;

//-----------------------------------------------------------------------------
//  JEDIプロジェクトを使用した解凍用のルーチン
//
//  ArchiveName   : 解凍するアーカイブのフルパス
//  DestDir       : 解凍先のフォルダ名
//  Password      : パスワードの文字列
//  ProgressEvent : OnProgressイベント用のメソッド名(必要なければnilにする)
//-----------------------------------------------------------------------------
function TForm1.ExtractArchive(ArchiveName, DestDir, Password: String;
  ProgressEvent: TJclCompressionProgressEvent=nil): Integer;
var
  LPassword      : String;
  LDestDir       : String;
  LArchiveName   : String;
  LFileExt       : String;
  LFileExtTar    : String;
  LFormats       : TJclDecompressArchiveClassArray;
  LArchive       : TJclCompressionArchive;
  LRetFormat     : Boolean;
  LTotalFileSize : Int64;
  i              : Integer;
begin
  Result := -1;

//  LIsTarXXX    := False;
  LPassword    := Password;
  LArchiveName := ArchiveName;
  LDestDir     := DestDir;
  LFileExt     := ExtractFileExt(LArchiveName);
  LFileExtTar  := '';

  //展開するファイルを格納するディレクトリを作成
  SysUtils.ForceDirectories(LDestDir);


  //.tar.gz, tgz等かを調べる
  //LFileExtTarが空文字のままなら.tar.gz, tgz等ではない
  LFileExtTar := ExtractFileExt(ExtractFileName(ChangeFileExt(LArchiveName, '')));
  if UpperCase(LFileExtTar) = '.TAR' then begin
    LFileExtTar := LFileExtTar + LFileExt;
  end else
  //拡張子が.txxの場合は.tarとそれ以後に分解する
  //まずxxの部分を解凍してからtarを展開するため
  if Pos('.T', UpperCase(LFileExt)) > 0 then begin
    if Pos('Z', UpperCase(LFileExt)) > 0 then begin
      LFileExtTar  := LFileExt;
      LArchiveName := ChangeFileExt(LArchiveName, '')
                    + '.tar'
                    + StringReplace(LFileExt, '.T', '.', [rfIgnoreCase]);
      LDestDir  := LDestDir + '.tar';
    end;
  end else begin
    //tar関係の書庫ではない場合
    LFileExtTar := '';
  end;


  //サポートしているフォーマットのリストを取得
  LFormats := GetArchiveFormats.FindDecompressFormats(LArchiveName);
  //使用可能なFormatを順番に検索していく
  LRetFormat := False;
  for i := Low(LFormats) to High(LFormats) do begin
    LArchive := LFormats[i].Create(LArchiveName, 0, False);

    LArchive.Password   := LPassword;
    LArchive.OnProgress := ProgressEvent;

    try
      //Formatが一致しないとListFiesで失敗する
      if LArchive is TJclDecompressArchive then begin
        TJclDecompressArchive(LArchive).ListFiles;
      end else
      if LArchive is TJclUpdateArchive then begin
        TJclUpdateArchive(LArchive).ListFiles;
      end;
      LRetFormat := True;
      break;
    except
      FreeAndNil(LArchive);
    end;
  end;

  if LRetFormat then begin
    try
      try
        Screen.Cursor := crHourGlass;

        //書庫内のファイルの解凍後のサイズの合計を取得
        LTotalFileSize := 0;
        for i := 0 to LArchive.ItemCount - 1 do begin
          if LArchive.Items[i].Attributes <> FILE_ATTRIBUTE_DIRECTORY then begin
            LTotalFileSize := LTotalFileSize + LArchive.Items[i].FileSize;
          end;
        end;
        //OnProgressイベント用のメソッドに合計ファイルサイズを知らせる
        if Assigned(LArchive.OnProgress) then begin
          LArchive.OnProgress(nil, -1, LTotalFileSize);
        end;

        //実際の展開・解凍(ExtractAllで全て展開)
        TJclDecompressArchive(LArchive).ExtractAll(LDestDir, True);


        //OnProgressの引数の値が不明確である対応策
        //OnProgressイベント用メソッドに最大値を送る
        if Assigned(LArchive.OnProgress) then begin
          LArchive.OnProgress(nil, LTotalFileSize, LTotalFileSize);
        end;

        //拡張子が.t.xxxの場合は,再度本ルーチンを実行して.tarを展開する
        if LFileExtTar <> '' then begin
          LArchiveName := IncludeTrailingPathDelimiter(LDestDir)
                        + ExtractFileName(LDestDir);
          Result := ExtractArchive(LArchiveName,
                                   ChangeFileExt(LDestDir, ''),
                                   LPassword,
                                   ProgressEvent);
          //tar.xxxのxxx部分のファイルはディレクトリごと削除
          TDirectory.Delete(LDestDir, True);
        end else begin
          Result := 0;
        end;
      except
        //書庫展開(解凍)失敗
        Result := 1;
      end;
    finally
      Screen.Cursor := crDefault;
      FreeAndNil(LArchive);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  TJclDecompressArchiveのOnProgressイベント処理
//  引数のValueは展開・解凍済みのファイルサイズの合計値らしい
//
//  Valueの値が-1の時はアーカイブ内のファイルサイズ(展開後)が取得できる
//  (展開用のプロシージャーから送るようにしている)
//-----------------------------------------------------------------------------
procedure TForm1.ArchiveProgress(Sender: TObject; const Value, MaxValue: Int64);
begin
  if Value < 0 then begin
    FTotalFileSize := MaxValue;
  end;
  if FTotalFileSize > 0 then begin
    //PostMessageでメッセージを送信して処理
    PostMessage(Handle, WM_SYNC_ARCHIVE_PROGRESS, Value, 0);
  end;
end;

//-----------------------------------------------------------------------------
//  OnProgressイベントからのメッセージ処理
//-----------------------------------------------------------------------------
procedure TForm1.WMSyncArchiveProgress(var Message: TMessage);
begin
  if FTotalFileSize > 0 then begin
    ProgressBar1.Position := Round(Message.WParam / FTotalFileSize * 1000);
  end;
end;

end.




03_専用のクラスを使用した圧縮・解凍


これまでは主にアーカイブの基本クラスを使用してます.
本サンプルでは,ZIP 書庫の圧縮と解凍を行いますが,圧縮には TJclZipCompressArchive とういう クラスを,解凍には TJclZipDecompressArchive という ZIP 書庫専用のクラスを使用しています.専用のクラスを使用すると,ほとんど型キャストが必要ありません.



図7
設計時画面
  • 書庫の作成・展開には 7z.dll が必要


リスト3
専用のクラスを使用した ZIP ファイルの圧縮・解凍
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls, JclCompression, Contnrs;

const
  WM_SYNC_ARCHIVE_PROGRESS = WM_USER + 153;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ProgressBar1: TProgressBar;
    Button2: TButton;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    FTotalFileSize : Int64;
    procedure WmSyncArchiveProgress(var Message: TMessage);
      message WM_SYNC_ARCHIVE_PROGRESS;
  public
    { Public 宣言 }
    procedure ArchiveProgress(Sender: TObject; const Value, MaxValue: Int64);
  end;

var
  Form1: TForm1;

implementation

uses
  FileCtrl;

{$R *.dfm}

//=============================================================================
//  圧縮(書庫作成)
//  フォルダの選択ダイアログで選択したフォルダ内全てのファイルを格納した書庫作成
//  書庫の作成先は,プロジェクトフォルダ内の[Temp]フォルダ
//  書庫ファイル名は対象のフォルダ名に拡張子を付加
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LArchiveFileName : String;
  LSourceDir       : String;
  LBaseDir         : String;
  LArchive         : TJclZipCompressArchive;
  LPassword        : String;
  i                : Integer;
begin
  LArchive  := nil;
  LPassword := '';
  ProgressBar1.Position := 0;
  ProgressBar1.Max      := 1000;

  LSourceDir := ExtractFileDir(Application.ExeName);
  if SelectDirectory('フォルダ選択', '', LSourceDir, [sdNewUI], Self) then begin

    //作成する書庫名のフルパス
    LBaseDir         := ExtractFilePath(Application.ExeName) + 'Temp';
    LArchiveFileName := IncludeTrailingPathDelimiter(LBaseDir)
                      + ExtractFileName(LSourceDir) + '.zip';
    //ディレクトリ作成
    SysUtils.ForceDirectories(ExtractFileDir(LArchiveFileName));

    Screen.Cursor := crHourGlass;
    //アーカイバのインスタンスを生成
    LArchive := TJclZipCompressArchive.Create(LArchiveFileName);
    if LArchive <> nil then begin
      try
        LArchive.Password   := LPassword;
        LArchive.OnProgress := ArchiveProgress;

        //指定したフォルダの階層下のファイル全てを書庫に追加
        LArchive.AddDirectory(ExtractFileName(LSourceDir), LSourceDir, True, True);

        //ファイルサイズの合計を取得
        FTotalFileSize := 0;
        for i := 0 to LArchive.ItemCount - 1 do begin
          if LArchive.Items[i].Attributes <> FILE_ATTRIBUTE_DIRECTORY then begin
            FTotalFileSize := FTotalFileSize + LArchive.Items[i].FileSize;
          end;
        end;

        //書庫作成
        //ここで実際に書庫が作成される
        LArchive.Compress;
      except
        MessageBox(Handle, '書庫作成失敗', '結果', MB_ICONSTOP);
      end;
    end else begin
      MessageBox(Handle, '未サポート書庫形式', '結果', MB_ICONWARNING);
    end;
  end;
  Screen.Cursor := crDefault;

  if Assigned(LArchive) then FreeAndNil(LArchive);
end;

//=============================================================================
//  ZIP書庫の解凍(展開)
//  展開先はプロジェクトフォルダ下の[Temp]フォルダ内
//  [Temp]フォルダ内にファイル名から拡張子を除いたフォルダを作成して格納
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LArchiveFileName : String;
  LBaseDir         : String;
  LDestDirectory   : String;
  LExtracter       : TJclZipDecompressArchive;
  LPassword        : String;
  i                : Integer;
begin
  LExtracter := nil;
  LPassword  := '';
  ProgressBar1.Position := 0;
  ProgressBar1.Max      := 1000;

  OpenDialog1.Filter     := 'ZIP 書庫|*.zip';
  OpenDialog1.InitialDir := ExtractFileDir(Application.ExeName);

  if OpenDialog1.Execute then begin
    //解凍(展開)対象の書庫ファイルのフルパス
    LArchiveFileName := OpenDialog1.FileName;

    //解凍(展開)先のフォルダ
    LBaseDir       := ExtractFilePath(Application.ExeName) + 'Temp';
    LDestDirectory := LBaseDir;
    //以下のようにすると2重にフォルダ名ができる(上のコードで書庫を作成した場合)
    //元のフォルダ名と別の場所に展開した場合でも,元のフォルダ名が分かる
    //LDestDirectory := IncludeTrailingPathDelimiter(LBaseDir)
    //                + ChangeFileExt(ExtractFileName(LArchiveFileName), '');

    Screen.Cursor := crHourGlass;
    //アーカイバのインスタンスを生成
    LExtracter := TJclZipDecompressArchive.Create(LArchiveFileName);
    if LExtracter <> nil then begin
      try
        LExtracter.Password   := LPassword;
        LExtracter.OnProgress := ArchiveProgress;

        //ListFilesメソッドを実行(展開先にファイルが追加可能になる)
        LExtracter.ListFiles;

        //書庫内のファイルの解凍後のサイズの合計を取得
        FTotalFileSize := 0;
        for i := 0 to LExtracter.ItemCount - 1 do begin
          if LExtracter.Items[i].Attributes <> FILE_ATTRIBUTE_DIRECTORY then begin
            FTotalFileSize := FTotalFileSize + LExtracter.Items[i].FileSize;
          end;
        end;

        //実際の解凍(ExtractAllで全て展開)
        LExtracter.ExtractAll(LDestDirectory, True);
      except
        MessageBox(Handle, '書庫展開 (解凍) 失敗', '結果', MB_ICONSTOP);
      end;
    end else begin
      MessageBox(Handle, '未サポート書庫形式', '結果', MB_ICONWARNING);
    end;
  end;
  Screen.Cursor := crDefault;

  if Assigned(LExtracter) then FreeAndNil(LExtracter);
end;

//-----------------------------------------------------------------------------
//  TJclZipCompressArchive, TJclZipDecompressArchiveのOnProgressイベント処理
//-----------------------------------------------------------------------------
procedure TForm1.ArchiveProgress(Sender: TObject; const Value, MaxValue: Int64);
begin
  if FTotalFileSize > 0 then begin
    //PostMessageでメッセージを送信して処理
    PostMessage(Handle, WM_SYNC_ARCHIVE_PROGRESS, Value, 0);
  end;
end;

//-----------------------------------------------------------------------------
//  OnProgressイベントからのメッセージ処理
//-----------------------------------------------------------------------------
procedure TForm1.WmSyncArchiveProgress(var Message: TMessage);
begin
  if FTotalFileSize > 0 then begin
    ProgressBar1.Position := Round(Message.WParam / FTotalFileSize * 1000);
  end;
end;

end.


JEDI Code Library v 2.7 (2014年10月28日現在) には,以下のような圧縮,解凍用のクラスが実装されています.



//    JEDI Code Library v 2.7
{*****************************************************************************
  Class hierarchy

  TJclCompressionStream
   |
   |-- TJclCompressStream
   |    |
   |    |-- TJclZLibCompressStream     handled by zlib http://www.zlib.net/
   |    |-- TJclBZIP2CompressStream    handled by bzip2 http://www.bzip.net/
   |    |-- TJclGZIPCompressStream     handled by zlib http://www.zlib.net/ + JCL
   |
   |-- TJclDecompressStream
        |
        |-- TJclZLibDecompressStream   handled by zlib http://www.zlib.net/
        |-- TBZIP2DecompressStream     handled by bzip2 http://www.bzip.net/
        |-- TGZIPDecompressStream      handled by zlib http://www.zlib.net/ + JCL

  TJclCompressionArchive
   |
   |-- TJclCompressArchive    handled by sevenzip http://sevenzip.sourceforge.net/
   |    |
   |    |-- TJclSevenzipCompressArchive
   |         |
   |         |-- TJclZipCompressArchive     
   |         |-- TJclBZ2CompressArchive     
   |         |-- TJcl7zCompressArchive      
   |         |-- TJclTarCompressArchive     
   |         |-- TJclGZipCompressArchive    
   |         |-- TJclXzCompressArchive      
   |         |-- TJclSwfcCompressArchive    
   |         |-- TJclWimCompressArchive     
   |
   |-- TJclDecompressArchive   handled by sevenzip http://sevenzip.sourceforge.net/
   |    |
   |    |-- TJclSevenZipDecompressArchive
   |         |
   |         |-- TJclZipDecompressArchive      
   |         |-- TJclBZ2DecompressArchive      
   |         |-- TJclRarDecompressArchive      
   |         |-- TJclArjDecompressArchive      
   |         |-- TJclZDecompressArchive        
   |         |-- TJclLzhDecompressArchive      
   |         |-- TJcl7zDecompressArchive       
   |         |-- TJclCabDecompressArchive      
   |         |-- TJclNsisDecompressArchive     
   |         |-- TJclLzmaDecompressArchive     
   |         |-- TJclLzma86DecompressArchive   
   |         |-- TJclPeDecompressArchive       
   |         |-- TJclElfDecompressArchive      
   |         |-- TJclMachoDecompressArchive    
   |         |-- TJclUdfDecompressArchive      
   |         |-- TJclXarDecompressArchive      
   |         |-- TJclMubDecompressArchive      
   |         |-- TJclHfsDecompressArchive      
   |         |-- TJclDmgDecompressArchive      
   |         |-- TJclCompoundDecompressArchive 
   |         |-- TJclWimDecompressArchive      
   |         |-- TJclIsoDecompressArchive      
   |         |-- TJclBkfDecompressArchive      
   |         |-- TJclChmDecompressArchive      
   |         |-- TJclSplitDecompressArchive    
   |         |-- TJclRpmDecompressArchive      
   |         |-- TJclDebDecompressArchive      
   |         |-- TJclCpioDecompressArchive     
   |         |-- TJclTarDecompressArchive      
   |         |-- TJclGZipDecompressArchive     
   |         |-- TJclXzDecompressArchive       
   |         |-- TJclNtfsDecompressArchive     
   |         |-- TJclFatDecompressArchive      
   |         |-- TJclMbrDecompressArchive      
   |         |-- TJclVhdDecompressArchive      
   |         |-- TJclMslzDecompressArchive    
   |         |-- TJclFlvDecompressArchive      
   |         |-- TJclSwfDecompressArchive      
   |         |-- TJclSwfcDecompressArchive     
   |         |-- TJclAPMDecompressArchive      
   |         |-- TJclPpmdDecompressArchive     
   |         |-- TJclTEDecompressArchive       
   |         |-- TJclUEFIcDecompressArchive    
   |         |-- TJclUEFIsDecompressArchive    
   |         |-- TJclSquashFSDecompressArchive 
   |         |-- TJclCramFSDecompressArchive   
   |
   |-- TJclUpdateArchive
        |
        |-- TJclSevenzipUpdateArchive
             |
             |-- TJclZipUpdateArchive       
             |-- TJclBZ2UpdateArchive       
             |-- TJcl7zUpdateArchive        
             |-- TJclTarUpdateArchive       
             |-- TJclGZipUpdateArchive      
             |-- TJclXzUpdateArchive        
             |-- TJclSwfcUpdateArchive      

******************************************************************************}




04_ZLIB ユニットを使用したファイルの圧縮・解凍


上の JEDI ライブラリに実装されているクラス名に Zlib というのがあります.
Zlib は,GZIP の主要開発者である Jean-Loup Gailly と Mark Adler らが,Deflate と呼ばれる処理のアルゴリズムだけをライブラリ化したものです.Zlib のアルゴリズムは,ZIP, GZIP 等で利用されています.また PNG 画像ファイルにも使用されています.

JEDI プロジェクトの Zlib のクラス,あるいは,この後のページの TurboPower Abbrevia を使用すると,この Zlib の機能を使用することができます.
Delphi には,この Zlib を利用するためのユニットが用意されています.JEDI プロジェクトを使用するまでもありません.本サンプルは,この Delphi に実装されている Zlib の簡単なテストです.と言っても,Delphi のオンラインヘルプのサンプルをそのまま利用しています.
Zlib にはアーカイブの機能はありません.単一ファイルの圧縮と解凍の機能だけです.

本サンプルでは,圧縮後のファイルと,元のファイルを別にしています.圧縮後のファイルの拡張子を,元のファイルと区別するために,.plz としています.



[備考]
本サンプルの実行に ZLIB.DLL は必要ありません.Delphi の Zlib ユニットは,ZLIB.DLL の機能を提供するものではありません.
Delphi のオンラインヘルプのサンプルでは,圧縮後のファイルの拡張子を .zip にしていますが,拡張子を .zip にしても ZIP 形式の書庫にはなりません.



図8
設計時画面
図9
約 10 MB のテキストファイルを圧縮した結果


リスト4
Zlib を使用した単一ファイルの圧縮と解凍
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FFileExt : String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ZLib;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  圧縮後のファイルの拡張子を設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FFileExt := '.plz';
end;

//=============================================================================
//  Zlibを使用したファイルの圧縮
//  usesにZLibが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBaseDir          : String;
  LSourceFileName   : String;
  LDestFileName     : String;
  LSourceFileStream : TFileStream;
  LDestFileStream   : TFileStream;
  LZCompress        : TZCompressionStream;
begin
  OpenDialog1.Filter := '|*.*|';
  if OpenDialog1.Execute then begin

    //圧縮対象のファイルのフルパス
    LSourceFileName := OpenDialog1.FileName;

    //圧縮したファイルの保存先のフォルダ名と圧縮後のファイル名のフルパス
    LBaseDir       := ExtractFilePath(Application.ExeName) + 'TempCompress';
    LDestFileName  := IncludeTrailingPathDelimiter(LBaseDir)
                    + ExtractFileName(LSourceFileName) + FFileExt;

    Screen.Cursor := crHourGlass;

    //圧縮対象のファイルのストリーム生成
    LSourceFileStream := TFileStream.Create(LSourceFileName, fmOpenRead);
    //圧縮語のファイルのストリーム生成
    LDestFileStream := TFileStream.Create(LDestFileName, fmCreate);
    try
      //TZCompressionStreamのインスタンスを生成
      LZCompress := TZCompressionStream.Create(clMax, LDestFileStream);
      try
        //圧縮実行
        LZCompress.CopyFrom(LSourceFileStream, LSourceFileStream.Size);
      finally
        //Freeすると実際にファイルに圧縮した内容が格納される
        FreeAndNil(LZCompress);
      end;
    finally
      LSourceFileStream.Free;
      LDestFileStream.Free;
    end;
    Screen.Cursor := crDefault;
  end;
end;

//=============================================================================
//  Zlibを使用して圧縮したファイルの解凍
//  usesにZLibが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LBaseDir          : String;
  LSourceFileName   : String;
  LDestFileName     : String;
  LSourceFileStream : TFileStream;
  LDestFileStream   : TFileStream;
  LZDeCompress      : TZDecompressionStream;
begin
  OpenDialog1.Filter     := '|*' + FFileExt + '|';
  OpenDialog1.InitialDir := ExtractFilePath(Application.ExeName) + 'TempCompress';

  if OpenDialog1.Execute then begin

    //解凍するファイルのフルパス
    LSourceFileName := OpenDialog1.FileName;

    //解凍後のファイルの保存先フォルダと解凍後のファイルのフルパス
    LBaseDir       := ExtractFilePath(Application.ExeName) + 'TempDeCompress';
    LDestFileName  := IncludeTrailingPathDelimiter(LBaseDir)
                    + ChangeFileExt(ExtractFileName(LSourceFileName), '');

    Screen.Cursor := crHourGlass;

    //解凍対象のファイルのストリームを生成
    LSourceFileStream := TFileStream.Create(LSourceFileName, fmOpenRead);
    //解凍後のファイルのストリームを生成
    LDestFileStream := TFileStream.Create(LDestFileName, fmCreate);
    try
      //TZDecompressionStreamのインスタンス生成
      LZDeCompress := TZDecompressionStream.Create(LSourceFileStream);
      try
        //解凍を実行
        LDestFileStream.CopyFrom(LZDeCompress, LZDeCompress.Size);
      finally
        //Freeすると実際にファイルに解凍した内容が格納される
        FreeAndNil(LZDeCompress);
      end;
    finally
      FreeAndNil(LDestFileStream);
      FreeAndNil(LSourceFileStream);
    end;
    Screen.Cursor := crDefault;
  end;
end;

end.