日曜日にBCB!



Do It Yourselfでひとつプログラムでも作ってみませんか。


第17話「圧縮分割機能 その1」

圧縮 ファイルをフロッピー転送する場合や、メールでやり取りする際、できるだけサイズを小さくしたいものです。寿分割を使っていても、まず圧縮してから分割することが多くなりがちです。だったら寿分割の中で圧縮してから分割出来ると便利です。また、圧縮機能を利用すると、複数のフォルダやファイルを圧縮結合してから寿分割で分割し、分割先で結合、解凍分解することができます。データ転送がとても楽になります。なお、転送先のパソコンに圧縮・解凍ソフトが入っていないことも考えられますので、自己解凍形式の圧縮も行います。

ファイル圧縮はいろいろなアーカイバ(圧縮・解凍)ソフトが出回っています。DLL(Dynamic Link Library)もフリーで公開されていますので、自分で圧縮プログラムを考えるよりはるかに手軽です。私はかなり昔からMICCOさんが作られたLHMeltsというフリーソフトを愛用しています。これは同じくMICCOさんが作られたLZH形式のアーカイバのUNLHA32.dllを利用しています。このアーカイバDLLは仕様が公開されているので、寿分割からも利用できます。嬉しいことにアーカイバDLLもフリーソフトです。このアーカイバを使って圧縮機能を追加したいと思います。

LZH形式は日本では広く利用されている形式ですが、海外ではZIP形式が一般的です。寿分割は未だ英語版にするつもりはありませんので、今回はLZH形式のみを使用します。なおLZH形式は海外でも利用できます。私は英語版WindowsNTでフリーソフトのPowerArchiverというソフトでLZH形式を圧縮・解凍しています。ご参考までに。


UNLHA32.dllの入手

今回使用するアーカイバDLLのUNLHA32.dllは仕様書と共にMICCOさんのホームページから入手できます。ダウンロードしてUNLHA32.dllを寿分割の開発フォルダにインストールします。本来はWindowsのシステムフォルダにインストールするのですが、寿分割は
  1. UNLHA32.dllがある ... 圧縮機能を利用できる
  2. UNLHA32.dllがない ... 圧縮機能を利用しない
とするため、プログラムの検証のため一旦開発フォルダに入れておきます。2.の動作を確認するためにはDLLを一旦ごみ箱に移します。DLLはプログラムの実行フォルダとシステムフォルダの両方でDLLを検索します。システムフォルダの他のDLLをを誤って削除するとWindowsが正しく動かなくなりますのでなるべくシステムフォルダは触らないようにしましょう。

UNLHA32.dll内の関数を利用するには関数のエクスポート定義を知る必要があります。そこでヘッダファイルunlha32.hを寿分割のメイン画面main.cppでインクルードします。なお、unlha32.hで使用されている時刻型time_ttime.hで定義されていますので、これもインクルードしておきます。
#include <vcl.h>
#include <stdio.h>
#include <math.h>
#include <shellapi.h>
#include <time.h>
#pragma hdrstop

#include "main.h"
#include "about.h"
#include "diskutil.h"
#include "setup.h"
#include "edit.h"
#include "unlha32.h"

アーカイブ仕様にはLHA.DLL互換APIとOpenArchive系APIの2系統があります。寿分割では圧縮に関して特に複雑な操作はしませんので、LHA.DLL互換APIを使用します。アーカイブ仕様に関してはUNLHA32.dllのパッケージに同梱されているApi.txtを参考にします。

圧縮DLLを寿分割から利用するにはアプリケーションのロード時にDLLをロードする静的にロードと、アプリケーションの実行中にDLLをロードする動的ロードがあります。上記のようにUNLHA32.dllがない場合は圧縮機能を利用しないだけにとどめ、寿分割の他の機能は有効にします。従って動的ロードを使用します。


圧縮関数

分割元ファイルの圧縮にはUNLHA32.dllの関数をいくつか利用します。関数の仕様はApi.txtを参照します。

関数 機能 引数 戻り値
int WINAPI Unlha( ) LHA.EXE 互換のコマンド文字列を与えて各種の書庫操作を行う _hwnd: UNLHA32.DLL を呼び出すアプリのウィンドウのハンドル、_szCmdLine: UNLHA32.DLL に渡すコマンド文字列、_szOutput: UNLHA32.DLL が結果を返すためのバッファ、_dwSize: バッファのサイズ 0=正常 / その他=エラー
int WINAPI UnlhaGetFileCount( ) 指定された書庫ファイルに格納されているファイル数を得る _szArcFile: 格納ファイル数を得たい書庫ファイル名 0以上=ファイル数 / -1=エラー

Unlha関数のコマンド文字列には以下を使用します。コマンドの意味はUNLHA32.dllのパッケージに同梱されているCommand.txtを参考にします。

操作 コマンド文字列 意味
圧縮 a -xr2 圧縮ファイルパス 基準フォルダ 圧縮ファイル(必要数列挙) -jf0 a: 書庫にファイルを追加、-xr2: フォルダ下のファイルをすべて格納 -jf0: 指定した基準ディレクトリからの相対パス記録
自己解凍書式の作成 s -gw4 圧縮ファイルパス 基準フォルダ s: 自己解凍書庫を作成、-gw4: WinSFX32M形式

圧縮ファイル数に上限は設けません。しかし、コマンド文字列の長さはMS-DOSの制約から最大半角255文字に制限されてしまいます。Command.txtには応答ファイルを用いて多量のファイルを圧縮すると書かれています。そこで、Unlha関数で応答ファイルを利用します。応答ファイルにには上記コマンド文字列中の圧縮ファイルパス・基準フォルダ・圧縮ファイル(必要数列挙)を以下の書式で記述します。
"圧縮ファイルパス"
"基準フォルダ"
"圧縮ファイル1"
"圧縮ファイル2"
"圧縮ファイル3"
      :

この時注意が必要です。コマンド引数は空白を区切りとします。パスやファイル名にロングファイル名で空白が使われていると、引数の区切りとされてしまいます。応答ファイルはダブルクオート(")で区切ります。従って圧縮コマンド文字列には「a -xr2 "@応答ファイルパス" -jf0」の文字列を与えます。
圧縮アーカイバ名と応答ファイル名をはシステム定数としてmain.hに定義しておきます。
#define MaxCutMenus 5
    :
#define CompressorDLL "Unlha32.dll" // 圧縮DLL
#define ResponceFile "kotocut.arf"  // 圧縮レスポンスファイル


Unlha関数でエラーが発生するとエラーコードが返ります。圧縮に関するエラーはエラーコードに応じてメッセージを表示します。エラーコードとエラー内容はUNLHA32.dllのパッケージに同梱されているApi.txtを参照します。

エラーコード エラーメッセージ
ERROR_DISK_SPACE 空き容量が不足しています
ERROR_READ_ONLY 圧縮ファイルに書き込めません
ERROR_CANNOT_WRITE
ERROR_FILE_OPEN 分割元ファイルを開けません
ERROR_NOT_FIND_FILE
ERROR_CANNOT_READ 分割元ファイルを読み込めません
ERROR_MORE_HEAP_MEMORY メモリ不足です
ERROR_ENOUGH_MEMORY
ERROR_TMP_OPEN 圧縮作業ファイルが作成できません
ERROR_NOT_FIND_ARC_FILE フォルダは空です
ERROR_SHARING ファイルにアクセスできません


インターフェース仕様

圧縮オプション

圧縮機能を利用する際、以下のオプションを設けます。 圧縮したファイルは寿分割と同じフォルダに作成します。ただし、デフォルトの分割先は分割元ファイルの先頭ファイルのフォルダとします。分割元ファイルを再度開いた時と、寿分割を終了する時に圧縮ファイルは削除します。

画面設計

圧縮機能のうち、圧縮の有無をメイン画面に、他の圧縮オプションを設定画面で行います。もう一つ追加画面があります。ファイルを圧縮すると大抵はファイルサイズが小さくなります。圧縮したファイルサイズと、どのファイルを圧縮したかを圧縮結果画面に表示します。

メイン画面に圧縮有無のメニューとツールボタンを追加します。以前均等分割機能を追加した手順と同じです。

Caption Name 内容
「分割(D)」 DivideMNU 分割操作に関するメニュー。
   :   
  「-」 SEP21 メニューを見やすくするセパレータ。
  「分割先(P)...」 PathMNU 分割先を変更。
  「圧縮して分割(C)」 CompressMNU 圧縮して分割するかのチェック。
  「均等に分割(E)」 EquallyMNU 均等に分割するかのチェック。
  「-」 SEP22 メニューを見やすくするセパレータ。


メニュー 有効イメージ 無効イメージ コメント
分割/圧縮して分割(C) 圧縮有効イメージ 圧縮無効イメージ  

圧縮アイコン、ビットマップのダウンロードはこちらからどうぞ。

圧縮タブ 設定画面に圧縮タブを設けます。ポイントは以下の4点です。


コントロール名 コントロール プロパティ イベント
CompressPAG TTabSheet Caption=圧縮  
SkipAtExtensionCHK TCheckBox Caption=単一ファイルで以下の拡張子は圧縮しない(&S)(半角) クリック
ExtensionEDT TEdit Hint=拡張子はカンマ','で区切ってください(半角)、ShowHint=true  
UncompressExtractCHK TCheckBox Caption=結合後、自動的に解凍する(&U)  
CompressMessage TLabel Caption=圧縮アーカイバ(UNLHA32.dll)が使用できません(半角)  


圧縮結果画面(テキストは出力例、サイズは若干小さい) Unlha関数で圧縮を行うと、圧縮結果のメッセージが返ってきます。ここには作成した圧縮ファイル名と圧縮したファイルの情報が記述されています。あと、作成した圧縮ファイルサイズをメッセージに追加して圧縮結果画面に表示します。 圧縮結果画面は、 とします。

コントロール名 コントロール プロパティ イベント
AResultDLG TForm Caption=圧縮結果、BorderStyle=bsDialog、Position=poScreenCenter、Icon=圧縮アイコン リサイズ
ControlPNL TPanel Caption=""、Align=alBottom、Height=28  
CloseBTN TButton Caption=閉じる、Default=true、ModalResult=mrOk  
ResultMEM TMemo Align=alClient、ScrollBar=ssBoth、ReadOnly=true  

初期化ファイル

初期化ファイルには圧縮情報を以下の形式で記録します。

セクション キー 内容
Compress Compress 論理値 true=圧縮 / false=非圧縮
SkipAtExtension 論理値 true=拡張子スキップあり / false=なし
SkipExtensions 文字列 スキップ拡張子列(','区切り)
Uncompress 論理値 true=自己解凍形式 / false=通常圧縮形式


データ管理変更

ファイル圧縮に伴い、いくつかのデータの管理方法を変更します。

圧縮情報の取り扱い

寿分割のパラメータに圧縮情報を追加します。スキップ拡張子リスト以外は論理値です。スキップ拡張子はBCBのTStringListで実装します。TStringListにはCommaTextという便利なプロパティーがあり、カンマ区切りの文字列の個々の要素をリストと相互に変換してくれます。
圧縮情報はメイン画面と設定画面上で設定します。圧縮可不可CompressEnabledはメイン画面の圧縮メニューが責任を持って管理します。単一ファイル指定拡張子スキップSkipAtExtension、スキップ拡張子リストSkipExtensions、結合後自動解凍UncompressExtractは設定画面で設定します。従って、寿分割パラメータに記録するのは設定画面の項目の指定拡張子スキップSkipAtExtension、スキップ拡張子リストSkipExtensions、結合後自動解凍SkipExtensionsです。ただし、設定画面に圧縮可不可を引き渡しますので、寿分割パラメータに圧縮可不可CompressEnabledも加えます。あくまでもメイン画面が優先です。データの責任を明らかにしておきましょう。さもないと、とんでもないバグが入りこんできます。第4話の「データの流れ道」を思い出してください。

改造設定パラメータ構造体

//---------------------------------------------------------------------------
// 設定パラメータ構造体
struct stKotoParam{
    TFont         *ViewFont;        // 表示フォント
    TColor        BGColor;          // 背景色
    bool          MenuVisible;      // メニュー表示
    bool          ToolBarVisible;   // ツールバー表示
    bool          StatusBarVisible; // ステータスバー表示
    enWINLOCATION WinLocation;      // ウィンド位置情報
    TStringList   *CutSizes;        // 分割サイズリスト
    bool          CompressEnabled;  // 圧縮可不可(メイン画面→設定画面)
    bool          SkipAtExtension;  // 単一ファイル指定拡張子スキップ
    TStringList   *SkipExtensions;  // スキップ拡張子リスト(初期化ファイル','区切り)
    bool          UncompressExtract;// 結合後、自動的解凍
};
//---------------------------------------------------------------------------

圧縮ファイル管理

圧縮指定によりファイルが圧縮→非圧縮→圧縮となる可能性もあります。従って、分割元ファイル名一覧と圧縮したファイル名を両方記録しておく必要が出てきました。そこで圧縮ファイル名の取り扱いについてルールを決めます。
これまでは分割元ファイル名オープンダイアログが責任をもって管理していました。分割元ファイル名一覧も圧縮ファイル名もオープンダイアログが責任を持って管理しますが、管理方法を変更します。また圧縮した分割元ファイルは分割後は不要となります。圧縮済みファイルがある/ないもオープンダイアログに管理させます。

プロパティ 変更前 変更後
Files 未使用 分割元ファイルリスト
FileName 分割元ファイル 非圧縮: 分割元ファイルリストの先頭と同じ、圧縮: 圧縮ファイル名パス
Tag 未使用 圧縮済みファイルがある: true、ない: false


設定画面の変更

さて、メイン画面から変更を加えて行きたいのですが、どうも変更箇所が多くてこのページで説明するのはスペース的に無理のようです。そこで設定画面から変更を加えます。

設定画面ではメイン画面とのデータのやり取りと、既に述べた画面設計のコールバック処理を実装します。

圧縮メッセージ設定

設定画面の圧縮メッセージCompressMessageは画面設計時に設定していますが、圧縮アーカイバは既にシステム定数として登録しています。同じ定義を複数箇所にするとプログラムのメンテナンスが面倒になります。そこで、システム定数で画面を更新します。面倒なようですが先々のことをしっかり考慮しておきましょう。

改造OnCreate関数
//---------------------------------------------------------------------------
void __fastcall TSetupDLG::OnCreate(TObject *Sender)
// 設定画面フォーム作成時コールバック
{
    FontNameBOX->Items->Clear();    // フォント名リストアイテムクリア
    ::EnumFontFamilies(GetDC(0), NULL, (FONTENUMPROC)EnumFontFamProc, NULL);   // フォントファミリー列挙
    CompressMessage->Caption = "圧縮アーカイバ(" + AnsiString(CompressorDLL) + ")が使用できません";   // アーカイバ名設定
}
//---------------------------------------------------------------------------
改造Execute関数

メイン画面からの呼出し関数に圧縮に関する設定を追加します。
//---------------------------------------------------------------------------
bool __fastcall TSetupDLG::Execute(TForm *_OwnerForm, stKotoParam *KotoParam)
// 設定実行
// _OwnerForm (TForm*)       呼出しフォーム
// KotoParam  (stKotoParam*) 寿分割設定パラメータ
// 戻り値     (bool)         true=設定実行/false=設定キャンセル
{
        :
    OnPositionGRPClick(PositionGRP);                             // ウィンド位置移動ボタン初期化

    SkipAtExtensionCHK->Checked = KotoParam->SkipAtExtension;       // スキップチェック設定
    ExtensionEDT->Text = KotoParam->SkipExtensions->CommaText;      // スキップ拡張子設定
    UncompressExtractCHK->Checked = KotoParam->UncompressExtract;   // 自己解凍チェック設定
    if (KotoParam->CompressEnabled){                                // 圧縮可能?
        SkipAtExtensionCHK->Enabled   = true;                           // スキップメッセージ不可
        ExtensionEDT->Enabled         = SkipAtExtensionCHK->Checked;    // 拡張子スキップリストエディット不可
        UncompressExtractCHK->Enabled = true;                           // 自己解凍メッセージ不可
        CompressMessage->Visible      = false;                          // 圧縮メッセージ非表示
    }
    else{                                                           // 圧縮不可?
        SkipAtExtensionCHK->Enabled   = false;                          // スキップメッセージ非表示
        ExtensionEDT->Enabled         = false;                          // 拡張子スキップリストエディット不可
        UncompressExtractCHK->Enabled = false;                          // 自己解凍メッセージ非表示
        CompressMessage->Visible      = true;                           // 圧縮メッセージ表示
    }
    ColorEDT(ExtensionEDT);                             // スキップ拡張子テキストボックス色設定

    ShowModal();     // モーダル入力

    if (ModalResult==mrOk){    // OKボタンが押されたら
        KotoParam->MenuVisible      = MenuVisibleCHK->Checked;           // メニュー表示登録
        KotoParam->ToolBarVisible   = ToolBarVisibleCHK->Checked;        // ツールバー表示登録
        KotoParam->StatusBarVisible = StatusBarVisibleCHK->Checked;      // ステータスバー表示登録
        KotoParam->ViewFont->Assign(SampleEDT->Font);                    // フォント登録
        KotoParam->BGColor = SampleEDT->Color;                           // 背景色登録
        KotoParam->WinLocation = GetWindowLocation();                    // ウィンド位置登録
        KotoParam->SkipAtExtension = SkipAtExtensionCHK->Checked;        // 圧縮スキップ登録
        KotoParam->UncompressExtract = UncompressExtractCHK->Checked;    // 自己解凍圧縮登録
        KotoParam->SkipExtensions->CommaText = ExtensionEDT->Text;       // スキップ拡張子リスト登録
    }

    return(ModalResult==mrOk);       // 設定実行またはキャンセルを返す
}
//---------------------------------------------------------------------------
ColorEDT関数

VCLでテキストボックスを無効(Enabled=false)にしても背景色はウィンド色のままです。文字色はグレーになるのですが、やっぱり入力できないテキストボックスは背景色もグレー(フレーム色)にしたいものです。多くのソフトもそのようにしています。テキストボックスの背景色をコントロールする関数を作りましょう。clWindowとclBtnFaceははWindowsのシステム色です。
//---------------------------------------------------------------------------
void __fastcall TSetupDLG::ColorEDT(TEdit *EDT)
{
    if (EDT->Enabled) EDT->Color = clWindow;    // エディットコントロール有効→ウィンド色
    else              EDT->Color = clBtnFace;   // エディットコントロール無効→フレーム色
}
//---------------------------------------------------------------------------
SkipAtExtensionCHKClick関数

設定画面の最後にスキップ条件のチェックがオン/オフされたときのコールバック処理をプログラミングします。スキップ条件のオン/オフでスキップ拡張子リストの有効/無効化します。同時に上記のスキップ拡張子テキストボックス色設定を行います。
//---------------------------------------------------------------------------
void __fastcall TSetupDLG::SkipAtExtensionCHKClick(TObject *Sender)
{
    ExtensionEDT->Enabled  = SkipAtExtensionCHK->Checked;    // スキップ拡張子テキストボックス有効/無効設定
    ColorEDT(ExtensionEDT);                                  // スキップ拡張子テキストボックス色設定
}
//---------------------------------------------------------------------------


圧縮レポート画面

もうひとつ圧縮レポート画面を作ります。フォームを新規作成しインターフェース仕様に則ってコントロールの配置、プロパティー設定を行います。プログラミングはさしたことはないのですが、ひとつだけ他の画面とは大きく異なることがあります。

動的インスタンス

寿分割に起動時パラメータで分割元ファイルを指定した場合、メイン画面のフォーム作成関数で圧縮を行います。圧縮の結果をレポート画面に表示するのですが、この時まだ圧縮レポート画面は作成されていません。話しがややこしいのですが、これがWindowsです。以下のソースを見てください。これはBCBが自動的に作成した寿分割の管理ソースです。プロジェクトからkotocut.cppを開くと見れます。通常は編集することはありません。
//---------------------------------------------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    try
    {
        Application->Initialize();
        Application->Title = "寿分割";
        Application->HelpFile = "kotocut.hlp";
        Application->CreateForm(__classid(TMainWND), &MainWND);
        Application->CreateForm(__classid(TSetupDLG), &SetupDLG);
        Application->CreateForm(__classid(TEditDLG), &EditDLG);
        Application->CreateForm(__classid(TAboutDLG), &AboutDLG);
        Application->CreateForm(__classid(TAResultDLG), &AResultDLG);
        Application->Run();
    }
    catch (Exception &exception)
    {
        Application->ShowException(&exception);
    }
    return 0;
}
//---------------------------------------------------------------------------
Application->CreateForm()がフォームを作っている処理です。最初に作られるフォームが最初に表示される画面です。5つの画面(メイン画面、設定画面、分割サイズ編集画面、バージョン情報画面、圧縮レポート画面)を作った後に、Application->Run()で寿分割を実行しています。これまでの方法では全ての画面が作成されないと、圧縮レポート画面は表示できないことになります。そこで、動的インスタンスと言う手法を用います。つまり、画面を必要なときに作って、必要がなくなったら破棄する手法です。まず、寿分割のプロジェクトオプションを変更します。

プロジェクトオプション


フォームの内、圧縮レポート画面(AResultDLG)を選択可能なフォームに移動します。圧縮レポート画面を使用する場合は自分で画面オブジェクトをインスタンスします。これでメイン画面の作成時に圧縮レポート画面を利用することができるようになります。

動的インスタンスの場合、利用する画面オブジェクトを必要時にインスタンスします。以下に例を示します。

    TAResultDLG *DLG = new TAResultDLG(NULL);       // 画面ダイアログインスタンス
    DLG->Execute();                                 // 画面ダイアログ実行(Executeはパブリック関数)
    delete DLG;                                     // 画面ダイアログ破棄

分割レポート画面を利用するメイン画面のプログラミングは次回に行います。

分割レポートの表示

メイン画面から分割レポートの文字列を受け取り、画面のResultMEMに表示します。表示したら画面をモーダルにして、「閉じる」ボタンの入力を待ちます。このメンバ関数はPublicとします。

Execute関数
//---------------------------------------------------------------------------
void __fastcall TAResultDLG::Execute(char *Message)
{
    ResultMEM->Lines->Text = Message;       // 圧縮レポートメッセージ設定
    ShowModal();                            // モーダル表示
}
//---------------------------------------------------------------------------

コントロール配置

「閉じる」ボタン以外のコントロールはAlignプロパティで自動的に配置しています。ボタンは画面をリサイズした際、プログラムで画面下中央に表示します。パネルはControlPNLAlign=alBottomで画面下に自動配置しているので、ボタンはパネル内の中央(左右均等位置)に配置します。配置の計算式は、"ボタンの左座標 = (パネルの幅 - ボタンの幅)/2"となります。

FormResize
//---------------------------------------------------------------------------
void __fastcall TAResultDLG::FormResize(TObject *Sender)
{
    CloseBTN->Left = (ControlPNL->Width - CloseBTN->Width)/2;       // パネルの中央に配置
}
//---------------------------------------------------------------------------

オマジナイ

以上で分割レポート画面のプログラミングは終了です。しかし、私は動的インスタンスするフォームでひとつオマジナイを入れておきます。通常自動作成の対象となっているダイアログオブジェクト(例えば寿分割のSetupDLG)は実行形式(Applicationオブジェクト)の中で一つインスタンスされ、そのポインタは必ず唯一のものを示します。しかし選択可能のダイアログではダイアログインスタンスが設定されているとは限りません。つまり動的インスタンスとなる圧縮レポートダイアログのオブジェクトAResultDLGは何も示していない可能性があるのです。これはBCB3特有かもしれません。そこでコンストラクタで自分自身のオブジェクトポインタthisAResultDLGに代入しておきます。

改造コンストラクタ
//---------------------------------------------------------------------------
__fastcall TAResultDLG::TAResultDLG(TComponent* Owner)
    : TForm(Owner)
{
    AResultDLG = this;
}
//---------------------------------------------------------------------------


設定画面、圧縮レポート画面の作成は以上で終わりです。次回はメイン画面に圧縮分割機能の追加を行います。圧縮分割の心臓部です。よろしくお付き合いください。

今回のプログラミング行数: 44行
今回までのプログラミング行数: 1,814行


目次に戻る目次に戻る トップに戻るトップに戻る