日曜日にBCB!



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


第18話「圧縮分割機能 その2」

圧縮 前回に引き続き圧縮分割機能をプログラミングします。今回はメイン画面への変更です。メイン画面の変更点は以下の通りです。


圧縮情報を初期化ファイルから取得

寿分割をはじめて起動する時、デフォルトの設定で初期化ファイルを作成します。圧縮情報を追加します。まず、セクション名をmain.hに追加します。
#define dfIniFileName "kotocut.ini"
    :
#define SizeSection "Size"
#define CompressSection "Compress"

初期化ファイル作成関数に圧縮情報を追加します。

改造CreateIniFile関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::CreateIniFile(AnsiString FileName)
{
        :
    IniFile->WriteBool(CompressSection, "Compress", false);                      // 分割元ファイル圧縮
    IniFile->WriteBool(CompressSection, "SkipAtExtension", false);               // 単一ファイルのみ圧縮
    IniFile->WriteString(CompressSection, "SkipExtensions", "lzh,zip,cab,jpg");  // スキップ拡張子リスト(',')区切り
    IniFile->WriteBool(CompressSection, "Uncompress", false);                    // 結合後自動解凍
    delete IniFile;        // 初期化ファイル終了
}
//---------------------------------------------------------------------------
圧縮情報はフォーム作成時に取得します。フォーム作成時には、 も行います。

改造FormCreate関数(前半)

//---------------------------------------------------------------------------
void __fastcall TMainWND::FormCreate(TObject *Sender)
{
// 分割サイズリスト作成
    KotoParam.CutSizes = new TStringList;

// 圧縮スキップ拡張子リスト作成
    KotoParam.SkipExtensions = new TStringList;

// 圧縮済みファイルなし
    OpenDLG->Tag = false;

// 初期設定
    AnsiString IniFilePath = ExtractFilePath(Application->ExeName) + dfIniFileName;  // 初期化ファイル名作成
    if (!FileExists(IniFilePath)) CreateIniFile(IniFilePath);   // 初期化ファイルがない場合は自動作成
    TIniFile *IniFile = new TIniFile(IniFilePath);              // 初期化ファイル取得
    GetCutSizes(IniFile);        // 分割サイズリスト取得
    StyleWindow(IniFile);        // ウィンド再現(フォント、位置、コントロール表示)
    GetCompress(IniFile);        // 圧縮指定取得
    delete IniFile;              // 初期化ファイル終了

    SetSizeMenu();                    // 分割サイズメニュー設定
    SizeMNUClick(Size1MNU);           // 初期分割サイズ→先頭
    DragAcceptFiles(Handle, true);    // ドロップ受け取り登録

    (以降はフォーム作成時に分割元ファイルの圧縮を行います)

GetCompress関数

UNLHA32.DLLが利用可能かを調べます動的ロードでは指定したDLLがロードできない場合はDLLのインスタンスハンドルにNULLが返ります。これから圧縮可不可を設定し、他は初期化ファイルから圧縮情報を得ます。
//---------------------------------------------------------------------------
void __fastcall TMainWND::GetCompress(TIniFile *IniFile)
{
    int WINAPI (*Unlha)(const HWND _hwnd, LPCSTR _szCmdLine, LPSTR _szOutput, const DWORD _dwSize) = NULL;
    // 関数のポインタ(NULL=なし)

// 圧縮アーカイバDLL検索
    bool Usable = false;
    HINSTANCE CompressorInstance = LoadLibrary(CompressorDLL);  // DLL動的ロード
    if (CompressorInstance!=NULL){                              // DLL使用可能
        Unlha = (int WINAPI (*)(const HWND, LPCSTR, LPSTR, const DWORD))GetProcAddress(CompressorInstance, "Unlha");
        // 関数取得
        if (Unlha!=NULL) Usable = true;                         // 圧縮使用不可
        FreeLibrary(CompressorInstance);                        // DLL解放
    }

// 圧縮可不可設定
    CompressMNU->Enabled = Usable;         // 圧縮メニュー可不可設定
    CompressBTN->Enabled = Usable;         // 圧縮ボタン可不可設定
    if (!Usable) CompressBTN->Hint = "圧縮アーカイバDLL(" + AnsiString(CompressorDLL) + ")が使用できません";

// 圧縮設定取得
    if (!IniFile->SectionExists(CompressSection)){             // セクションがない(初めて起動、バージョンアップ)?
        Application->MessageBox("圧縮設定を自動的に行います",  // バージョンアップメッセージ表示(半角)
                                "寿分割バージョンアップ",
                                MB_OK);
        IniFile->WriteString(CompressSection, "SkipExtensions", "lzh,zip,cab,jpg");  // スキップ拡張子リスト(','区切り)
    }
    if (CompressMNU->Enabled){      // 圧縮可能なら圧縮分割指定を読み取る
        CompressMNU->Checked = IniFile->ReadBool(CompressSection, "Compress", false);        // 分割元ファイル圧縮設定取得
        CompressBTN->Down = CompressMNU->Checked;
    }
    KotoParam.SkipAtExtension = IniFile->ReadBool(CompressSection, "SkipAtExtension", false);           // 単一ファイルのみ圧縮
    KotoParam.SkipExtensions->CommaText = IniFile->ReadString(CompressSection, "SkipExtensions", "");   // スキップ拡張子リスト
    KotoParam.UncompressExtract = IniFile->ReadBool(CompressSection, "Uncompress", false);              // 結合後自動解凍
}
//---------------------------------------------------------------------------


分割元ファイルの圧縮

分割元ファイルを圧縮するのは、 の3箇所です。

フォーム作成時

フォームの作成時に以下の変更を行います。
  1. コマンド引数からファイル名をオープンダイアログに設定
  2. 圧縮指定の場合はファイルを圧縮
ここで重要な変更があります。これまではOpenDLG->FileNameから分割先パスを作成していました。しかし、圧縮ファイルは寿分割フォルダに一時的に置き、その後分割します。OpenDLG->FileNameは圧縮ファイルのパスを示していますので、分割先パスは分割ファイルリストの先頭OpenDLG->Files->Strings[0]から取得するように変更します。これはファイル読込み時とファイルドラッグアンドドロップ時にも適用します。

改造FormCreate関数(後半)

    (圧縮情報取得の続きです)

// 分割ファイル起動時引数
    OpenDLG->Files->Clear();                        // 分割元ファイルリストクリア
    for (int i=1;i<=ParamCount();i++){              // コマンド引数ループ
        AnsiString LongName = GetLongPathName(ParamStr(i)); // ショート→ロングファイル名変換
        if (LongName!="") OpenDLG->Files->Add(LongName);    // ロングファイル名あり
    }
    if (OpenDLG->Files->Count>0){                   // 分割ファイルあり?
        OpenDLG->FileName = OpenDLG->Files->Strings[0];     // 分割先ファイル名設定(非圧縮/圧縮)
        if (CheckSourceFiles()){                    // 分割可能、または非圧縮?
            Show();                                 // 寿分割表示
            Update();                               // 画面更新
            if (CompressMNU->Checked){              // 圧縮分割?
                if (CompressSourceFiles()){             // 分割元ファイル圧縮
                    ListDivideFiles(OpenDLG->FileName); // 分割先ファイル一覧および結合バッチファイル名を設定
                    SetupDestPath(OpenDLG->Files->Strings[0]);    // 分割先パスをステータスバーに設定。
                }
            }
            else{                                   // 非圧縮?
                ListDivideFiles(OpenDLG->FileName);     // 分割先ファイル一覧および結合バッチファイル名を設定
                SetupDestPath(OpenDLG->Files->Strings[0]);        // 分割先パスをステータスバーに設定。
            }
        }
    }

    UpdateMenu();    // メニュー更新
}
//---------------------------------------------------------------------------

CheckSourceFiles関数

非圧縮の場合、複数ファイルやフォルダは指定できません。事前にチェックします。
//---------------------------------------------------------------------------
bool __fastcall TMainWND::CheckSourceFiles()
{
// 圧縮の場合は処理終了
    if (CompressMNU->Checked) return(true);

// 複数ファイル選択チェック
    if (OpenDLG->Files->Count>1){      // 非圧縮で複数ファイルは分割不可
        Application->MessageBox("非圧縮で複数ファイルを分割することは出来ません", "分割元ファイル", MB_ICONEXCLAMATION);
        return(false);
    }

// フォルダ選択チェック
    if ((FileGetAttr(OpenDLG->Files->Strings[0]) & faDirectory)==faDirectory){   // 非圧縮でディレクトリは分割不可
        Application->MessageBox("非圧縮でフォルダを分割することは出来ません", "分割元ファイル", MB_ICONEXCLAMATION);
        return(false);
    }
    return(true);
}
//---------------------------------------------------------------------------

「ファイル」→「開く」メニュー

まず、オープンダイアログで複数ファイルを選択できるようにプロパティーを設定します。 ファイルを開く処理に変更を行います。

改造LoadMNUClick関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::LoadMNUClick(TObject *Sender)
{
    if (OpenDLG->Execute()){    // オープンダイアログを実行し、分割元ファイルパス+名前を取得
        if (!CheckSourceFiles()) return;            // 分割チェック
        ClearCompressedFile();                      // 圧縮済みファイルクリア
        if (CompressMNU->Checked){
            if (!CompressSourceFiles()) return;     // 分割元ファイル圧縮
        }
        ListDivideFiles(OpenDLG->FileName);         // 分割先ファイル一覧および結合バッチファイル名を設定
        SetupDestPath(OpenDLG->Files->Strings[0]);  // 分割先パスをステータスバーに設定。
    }
    UpdateMenu();
}
//---------------------------------------------------------------------------

圧縮済みファイルクリア

圧縮済みファイルは新規作成時、ファイル読込み時、ファイルドラッグアンドドロップ時、圧縮メニュー・ボタンの更新時、寿分割終了時に削除します。

ClearCompressedFile関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::ClearCompressedFile()
{
    if (!OpenDLG->Tag) return;              // 圧縮済みファイルなし

    if (OpenDLG->FileName!=""){             // 圧縮済みファイルあり?
        if (!DeleteFile(OpenDLG->FileName)){    // 圧縮済みファイル削除
            AnsiString Message = "圧縮ファイル'" + OpenDLG->FileName + "'を削除できません";     // 圧縮エラー表示
            Application->MessageBox(Message.c_str(), "分割実行", MB_ICONEXCLAMATION | MB_OK);
        }
    }
    OpenDLG->Tag = false;                   // 圧縮済みファイルなし
}
//---------------------------------------------------------------------------

ファイルドラッグアンドドロップ時

メッセージから取得したファイル数を元にファイルリストをオープンダイアログに設定します。ドロップ処理に変更を行います。

改造OnDropFiles関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::OnDropFiles(TWMDropFiles Message)
{
    char FileName[256];                         // メッセージからファイル名を受け取るバッファ
    int NumFiles = DragQueryFile((HDROP)Message.Drop, 0xFFFFFFFF, NULL, 0);    // メッセージからファイル数を受け取る
    ClearCompressedFile();                      // 圧縮済みファイルクリア
    OpenDLG->Files->Clear();                    // 分割元ファイルリストクリア
    for (int i=0;i<NumFiles;i++){               // メッセージファイルループ
        DragQueryFile((HDROP)Message.Drop, i, FileName, 255);    // メッセージからファイル名を受け取る
        OpenDLG->Files->Add((AnsiString)FileName);   // 分割ファイルリスト追加
    }
    DragFinish((HDROP)Message.Drop);            // ドロップのためにWindowsが使用したメモリを解放
    Application->BringToFront();                // 寿分割を最前面に移動
    OpenDLG->FileName = OpenDLG->Files->Strings[0];    // 分割先ファイル名設定
    if (!CheckSourceFiles()) return;            // 分割可能、または非圧縮?
    if (CompressMNU->Checked){                  // 圧縮?
        if (!CompressSourceFiles()) return;     // 分割元ファイル圧縮
    }
    ListDivideFiles(OpenDLG->FileName);         // 分割先ファイル一覧および結合バッチファイル名を設定
    SetupDestPath(OpenDLG->Files->Strings[0]);  // 分割先パスをステータスバーに設定。
    UpdateMenu();                               // メニュー更新
}
//---------------------------------------------------------------------------

さて、もっとも肝心な分割元ファイル圧縮です。ファイル圧縮では、 を行います。圧縮は圧縮コマンドを作成し、Unlha32.DLLを呼び出します。圧縮したファイルは圧縮情報に従ってファイル名を設定します。

CompressSourceFiles関数

//---------------------------------------------------------------------------
bool __fastcall TMainWND::CompressSourceFiles()
{
    int WINAPI (*Unlha)(const HWND _hwnd, LPCSTR _szCmdLine, LPSTR _szOutput, const DWORD _dwSize);     // 圧縮関数ポインタ
    int WINAPI (*UnlhaGetFileCount)(LPCSTR _szArcFile);                                                 // 圧縮ファイル数関数ポインタ

// 圧縮チェック
    if ((OpenDLG->Files->Count==1) && (KotoParam.SkipAtExtension)){     // 単一圧縮かつスキップ条件あり
        bool Skip = false;                                      // スキップフラグ→オフ
        for (int i=0;i<KotoParam.SkipExtensions->Count;i++){    // スキップ拡張子リスト比較ループ
            if (("."+KotoParam.SkipExtensions->Strings[i])==ExtractFileExt(OpenDLG->Files->Strings[0])) Skip = true;
        }                                                       // スキップ拡張子に該当
        if (Skip){                                              // 圧縮スキップ?
            CompressMNU->Checked = false;                       // 圧縮メニューオフ
            CompressBTN->Down    = false;                       // 圧縮ボタンオフ
            Application->MessageBox("拡張子指定により圧縮をスキップしました", "圧縮分割", MB_ICONQUESTION | MB_OK); // スキップメッセージ表示
            return(true);                                       // 圧縮エラー無し
        }
    }

// 圧縮アーカイバDLL取得
    bool Usable = false;                                        // 圧縮アーカイバ使用可フラグ→オフ
    HINSTANCE CompressorInstance = LoadLibrary(CompressorDLL);  // DLL動的ロード
    if (CompressorInstance!=NULL){                              // DLL使用可能?
        Unlha = (int WINAPI (*)(const HWND, LPCSTR, LPSTR, const DWORD))GetProcAddress(CompressorInstance, "Unlha"); // 圧縮関数取得
        if (Unlha!=NULL) Usable = true;                         // DLL使用可能
    }
    if (!Usable){                                               // DLLが使用不可の場合
        if (CompressorInstance!=NULL) FreeLibrary(CompressorInstance);  // DLL解放
        AnsiString Message = "圧縮アーカイバDLL(" + AnsiString(CompressorDLL) + ")が使用できません";
        Application->MessageBox(Message.c_str(), "圧縮分割", MB_ICONSTOP | MB_OK);      // 使用不可メッセージ表示
        CompressMNU->Enabled = false;                           // 圧縮メニュー不可
        CompressBTN->Enabled = false;                           // 圧縮ボタン不可
        return(false);                                          // 圧縮エラーあり
    }

// 圧縮
    AnsiString ResponseFilePath = MakeResponceFile();           // 圧縮応答ファイル作成
    if (ResponseFilePath==""){                                  // 圧縮応答ファイル作成エラー発生?
        FreeLibrary(CompressorInstance);                        // DLL解放
        AnsiString Message = "圧縮応答ファイルが作成できません";    
        Application->MessageBox(Message.c_str(), "圧縮分割", MB_ICONSTOP | MB_OK);  // エラーメッセージ表示
        CompressMNU->Enabled = false;                           // 圧縮メニュー不可
        CompressBTN->Enabled = false;                           // 圧縮ボタン不可
        return(false);                                          // 圧縮エラーあり
    }
    AnsiString Command = "a -xr2 \"@" + ResponseFilePath + "\" -jf0";   // 圧縮コマンド作成
    char *StatusBuffer = new char[AResultMessageLength];                // 応答レポート文字列領域確保
    strcpy(StatusBuffer, "");                                           // 圧縮レポート文字列クリア
    bool StayOnTop = StayOnTopMNU->Checked;                      // 画面表示状態退避
    FormNonTop(MainWND->Handle);                                 // 通常表示
    Application->ProcessMessages();                              // 画面状態更新
    int Status = Unlha(Application->Handle, Command.c_str(), StatusBuffer, AResultMessageLength);   // ファイル圧縮実行
    if (StayOnTop) FormOnTop(MainWND->Handle);                   // 常に前に表示
    if (Status!=0){                                              // 圧縮エラー発生?
        FreeLibrary(CompressorInstance);                         // DLL解放
        delete StatusBuffer;                                     // 圧縮レポートバッファ領域解放
        ShowCompressedError(Status);                             // 圧縮エラー表示
        CompressMNU->Checked = false;                            // 圧縮メニューオフ
        CompressBTN->Down = false;                               // 圧縮ボタンオフ
        return(false);                                           // 圧縮エラーあり
    }
    UnlhaGetFileCount = (int WINAPI (*)(LPCSTR))GetProcAddress(CompressorInstance, "UnlhaGetFileCount");  // 圧縮ファイル数取得
    AnsiString CompressedName = ChangeFileExt(OpenDLG->FileName, ".lzh");                       // 圧縮ファイル名作成
    CompressedName = ExtractFilePath(Application->ExeName) + ExtractFileName(CompressedName);   // 寿分割フォルダに変更
    if (UnlhaGetFileCount(CompressedName.c_str())<=0){           // 圧縮ファイルが空?
        delete StatusBuffer;                                     // 圧縮レポートバッファ領域解放
        Status = ERROR_NOT_FIND_ARC_FILE;                        // 圧縮エラーに'書庫がありませんでした'のエラーを設定
        ShowCompressedError(Status);                             // 圧縮エラー表示
        return(false);                                           // 圧縮エラーあり
    }
    FreeLibrary(CompressorInstance);                             // DLL解放

// 自己解凍作成
    if (KotoParam.UncompressExtract){                            // 自己解凍分割ファイルを作成?
        if (MakeSFX(CompressedName)){                               // 自己解凍圧縮ファイルを作成
            CompressedName = ChangeFileExt(CompressedName, ".exe");     // 拡張子を'exe'に変更
            strcat(StatusBuffer, "\r\n自己解凍圧縮ファイルを作成しました\r\n"); // 圧縮レポートメッセージに自己解凍作成を追加
        }
        else{
            return(false);                                       // 圧縮エラーあり
        }
    }

// 圧縮レポート表示
    AnsiString SizeMessage = "\r\n圧縮ファイルサイズ = " + FormatD2T((double)GetFileSize(CompressedName)) + " Byte";  // サイズメッセージ作成
    strcat(StatusBuffer, SizeMessage.c_str());                  // 圧縮レポートメッセージにサイズを追加
    AResultDLG = new TAResultDLG(NULL);                         // 圧縮レポート画面インスタンス
    AResultDLG->Execute(StatusBuffer);                          // 圧縮レポート表示
    delete AResultDLG;                                          // 圧縮レポート画面破棄

    delete StatusBuffer;                                        // 圧縮レポート文字列バッファ破棄

    OpenDLG->FileName = CompressedName;                         // 圧縮ファイル名登録

// 圧縮済みファイルあり
    OpenDLG->Tag = true;

    return(true);                   // 圧縮成功
}
//---------------------------------------------------------------------------

MakeResponceFile関数

圧縮応答ファイルを作成します。
//---------------------------------------------------------------------------
AnsiString __fastcall TMainWND::MakeResponceFile()
{
    AnsiString ResponseFilePath = ExtractFilePath(Application->ExeName) + ResponceFile;     // 応答ファイル名作成
    FILE *fp = fopen(ResponseFilePath.c_str(), "wt");                                       // 応答ファイル(テキスト)作成
    if (fp==NULL) return("");                                                               // 作成失敗?

    AnsiString CompressedName = ChangeFileExt(OpenDLG->FileName, ".lzh");                   // 圧縮ファイル名取得
    CompressedName = ExtractFilePath(Application->ExeName) + ExtractFileName(CompressedName);   // 圧縮ファイルパスを寿分割のフォルダに変更
    AnsiString BasePath = ExtractFilePath(OpenDLG->Files->Strings[0]);                      // 圧縮元ファイルパス取得
    int BasePathLen = BasePath.Length();                                                    // 圧縮元ファイルパス長さ取得

    if (fprintf(fp, "\"")==EOF) goto error;                             // 「"圧縮ファイルパス"(改行)」作成
    if (fprintf(fp, CompressedName.c_str())==EOF) goto error;
    if (fprintf(fp, "\"\n\"")==EOF) goto error;
    if (fprintf(fp, BasePath.c_str())==EOF) goto error;                 // 「基準フォルダ"(改行)」作成
    if (fprintf(fp, "\"\n")==EOF) goto error;
    for (int i=0;i<OpenDLG->Files->Count;i++){                          // 圧縮ファイルループ
        AnsiString FilesInPath = OpenDLG->Files->Strings[i];                // 圧縮ファイルパス取得
        if ((FileGetAttr(FilesInPath) & faDirectory)==faDirectory){         // フォルダの場合は'\*.*'を追加
            FilesInPath = FilesInPath + "\\*.*";
        }
        if (fprintf(fp, "\"")==EOF) goto error;                             // 「"(パスを除く圧縮ファイル名)"(改行)」作成
        if (fprintf(fp, FilesInPath.SubString(BasePathLen+1, FilesInPath.Length()-BasePathLen).c_str())==EOF) goto error;
        if (fprintf(fp, "\"\n")==EOF) goto error;
    }
    if (fclose(fp)==EOF) goto error;                                    // 応答ファイルクローズ

    return(ResponseFilePath);                                           // 応答ファイル名を返す

error:      // エラー処理
    fclose(fp);         // 応答ファイルクローズ
    return("");         // エラーを返す
}
//---------------------------------------------------------------------------

MakeSFX関数

LZH圧縮ファイルを自己解凍に変換します。これも圧縮同様Unlha関数を使用します。
//---------------------------------------------------------------------------
bool __fastcall TMainWND::MakeSFX(AnsiString ArchiveName)
{
    int WINAPI (*Unlha)(const HWND _hwnd, LPCSTR _szCmdLine, LPSTR _szOutput, const DWORD _dwSize);     // 圧縮関数ポインタ

// 圧縮アーカイバDLL取得
    bool Usable = false;                                        // 圧縮アーカイバ使用可フラグ→オフ
    HINSTANCE CompressorInstance = LoadLibrary(CompressorDLL);  // DLL動的ロード
    if (CompressorInstance!=NULL){                              // DLL使用可能?
        Unlha = (int WINAPI (*)(const HWND, LPCSTR, LPSTR, const DWORD))GetProcAddress(CompressorInstance, "Unlha"); // 圧縮関数取得
        if (Unlha!=NULL) Usable = true;                         // DLL使用可能
    }
    if (!Usable){                                               // DLLが使用不可の場合
        if (CompressorInstance!=NULL) FreeLibrary(CompressorInstance);  // DLL解放
        AnsiString Message = "圧縮アーカイバDLL(" + AnsiString(CompressorDLL) + ")が使用できません";
        Application->MessageBox(Message.c_str(), "自己解凍ファイル作成", MB_ICONSTOP | MB_OK);   // 使用不可メッセージ表示
        return(false);                                          // 圧縮エラーあり
    }

// 自己解凍形式作成
    AnsiString Command = "s -gw4 \"" + ArchiveName + "\" \"" + ExtractFilePath(ArchiveName);    // 自己解凍作成コマンド作成
    int Status = Unlha(Application->Handle, Command.c_str(), NULL, 0);      // 自己解凍形式作成
    FreeLibrary(CompressorInstance);                            // DLL解放
    DeleteFile(ArchiveName);                                    // LZH圧縮ファイル削除
    if (Status!=0){                                             // 自己解凍作成エラー?
        ShowCompressedError(Status);                            // 圧縮エラー表示
        return(false);                                          // 自己解凍作成エラー
    }

    return(true);                                                // 自己解凍作成成功
}
//---------------------------------------------------------------------------

ShowCompressedError関数

圧縮時に発生したエラーを表示します。Unlha関数はさまざまなエラーをステータスコードで返します(Unlha32.dllに同梱のapi.txtに記述されています)。このうち、書庫作成に関するエラーを寿分割で表示します。
//---------------------------------------------------------------------------
void __fastcall TMainWND::ShowCompressedError(int Status)
{
    AnsiString Message = "";            // エラーメッセージクリア
    switch (Status){
        case ERROR_DISK_SPACE:          Message = "空き容量が不足しています";           break;
        case ERROR_READ_ONLY:           Message = "圧縮ファイルに書き込めません";       break;
        case ERROR_FILE_OPEN:
        case ERROR_NOT_FIND_FILE:
           Message = "分割元ファイルを開けません";
           break;
        case ERROR_CANNOT_WRITE:        Message = "圧縮ファイルに書き込めません";       break;
        case ERROR_CANNOT_READ:         Message = "分割元ファイルを読み込めません";     break;
        case ERROR_MORE_HEAP_MEMORY:
        case ERROR_ENOUGH_MEMORY:
            Message = "メモリ不足です";
            break;
        case ERROR_TMP_OPEN:            Message = "圧縮作業ファイルが作成できません";   break;
        case ERROR_NOT_FIND_ARC_FILE:   Message = "フォルダは空です";                   break;
        case ERROR_SHARING:             Message = "ファイルにアクセスできません";       break;
    }
    if (Message!="") Application->MessageBox(Message.c_str(), "分割元ファイル", MB_ICONEXCLAMATION);    // エラーがある場合は表示
}
//---------------------------------------------------------------------------

改造NewMNUClick関数

ファイル新規作成関数に圧縮ファイルのクリア処理を追加します。
//---------------------------------------------------------------------------
void __fastcall TMainWND::NewMNUClick(TObject *Sender)
{
    ClearCompressedFile();      // 圧縮済みファイルクリア
    OpenDLG->FileName = "";     // 分割元ファイルパス+名前をクリア
    OpenDLG->Files->Clear();    // 分割元ファイルリストクリア
    ListDivideFiles(OpenDLG->FileName);    // 分割先ファイル一覧および結合バッチファイル名を設定
    SetupDestPath(OpenDLG->FileName);      // 分割先パスをステータスバーに設定。
    UpdateMenu();
}
//---------------------------------------------------------------------------


結合バッチファイルの作成

自己解凍形式では結合コマンドの最後に圧縮ファイル(Execute形式)を実行するコマンドを追加します。

改造MakeConnectBatch関数

//---------------------------------------------------------------------------
AnsiString __fastcall TMainWND::MakeConnectBatch(AnsiString FilePath, int NumCut)
{
        :

    for (int i=2;i<NumCut;i++){    // 003以降の結合コマンド作成
        Batch = Batch + "copy /b \"" + FileName  + "\"+\""
                      + ChangeFileExt(FileName, MakeOrderExt(i)) + "\" \""
                      + FileName + "\"\r\n";
    }


    if ((KotoParam.UncompressExtract) && (OpenDLG->Tag)){           // 自己解凍形式の作成かつ圧縮ファイルあり?
        Batch = Batch + "\"" + FileName + "\"\r\n";                 // 自己解凍形式の実行コマンドを追加
    }
    return(Batch);    // 結合コマンドテキストを返す
}
//---------------------------------------------------------------------------


圧縮メニュー・ボタンの更新

メニューとボタンのチェックが変更された場合の処理を行います。以前均等分割機能を追加した手順と同じです。ただ、チェックを変更するだけでは済みません。既に分割元ファイルが開かれている場合、圧縮と非圧縮を切り替える必要があります。単に「今は変更できません」で済ませれば簡単なのですが、それでは使い手が悪くなってしまいます。ここで圧縮と非圧縮の切り替え条件を整理します。

変更前 切り替え 変更後
非圧縮(単一ファイル) 確認後圧縮 圧縮(単一ファイル)
圧縮(単一ファイル) 確認後非圧縮 非圧縮(単一ファイル)
圧縮(複数ファイル) 不可

CompressMNUClick関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::CompressMNUClick(TObject *Sender)
{
// コントロール設定
    bool Change = false;                        // 非圧縮←→圧縮変更フラグ←オフ
    TMenuItem *ITEM = dynamic_cast<TMenuItem*>(Sender);    // メニューコントロール取得
    if (ITEM!=NULL){                                // メニューが選択された
        ITEM->Checked = (!ITEM->Checked);           // メニューチェックの反転
        CompressBTN->Down = ITEM->Checked;          // ボタンチェック反転
        Change = true;                              // 変更
    }
    TToolButton *BTN = dynamic_cast<TToolButton*>(Sender);    // ボタンコントロール取得
    if (BTN!=NULL){                                 // ボタンが選択された
        CompressMNU->Checked = BTN->Down;           // メニューチェック←ボタンチェック
        Change = true;                              // 変更
    }

// 分割ファイルリスト圧縮←→非圧縮変換
    if (Change && (ListVEW->Items->Count>0)) ChangeCompress();      // 非圧縮←→圧縮変更
}
//---------------------------------------------------------------------------

ChangeComppress関数

ファイルの非圧縮←→圧縮を変更します。メニューのコールバックのCompressMNUClick関数と独立関数としているのは将来の機能アップに対応するためです。現在は設定画面で圧縮に関するオプションを変更しても、分割済みのファイルを再度設定はしません。将来はこの機能もつけたいと思っています。(^^;
//---------------------------------------------------------------------------
void __fastcall TMainWND::ChangeCompress()
{
    if (CompressMNU->Checked){                // 非圧縮→圧縮
        int YesNo = Application->MessageBox("現在のファイルを圧縮しますか?", "圧縮分割", MB_ICONQUESTION | MB_YESNO);
        if (YesNo==IDYES){                    // 圧縮実行
            OpenDLG->FileName = OpenDLG->Files->Strings[0]; // 圧縮ファイル解除
            if (CompressSourceFiles()){             // 分割元ファイル圧縮
                ListDivideFiles(OpenDLG->FileName); // 分割先ファイル一覧および結合バッチファイル名を設定
            }
        }
        else{                                 // 圧縮キャンセル
            CompressMNU->Checked = false;           // 圧縮メニューオフ
            CompressBTN->Down = false;              // 圧縮ボタンオフ
        }
    }
    else{                                   // 圧縮→非圧縮
        if (!CheckSourceFiles()){               // 非圧縮不可(複数ファイル)?
            CompressMNU->Checked = true;            // 圧縮メニューオン
            CompressBTN->Down    = true;            // 圧縮ボタンオン
            return;                                 // 非圧縮をキャンセルにして圧縮を保持
        }
        int YesNo = Application->MessageBox("現在のファイルを非圧縮にしますか?", "圧縮分割", MB_ICONQUESTION | MB_YESNO);
        if (YesNo==IDYES){                      // 非圧縮実行
            ClearCompressedFile();                      // 圧縮済みファイルクリア
            OpenDLG->FileName = OpenDLG->Files->Strings[0]; // 圧縮ファイル解除
            ListDivideFiles(OpenDLG->FileName);     // 分割先ファイル一覧および結合バッチファイル名を設定
        }
        else{                                   // 非圧縮キャンセル
            CompressMNU->Checked = true;            // 圧縮メニューオン
            CompressBTN->Down    = true;            // 圧縮ボタンオン
        }
    }
}
//---------------------------------------------------------------------------


設定画面の呼出し

設定画面を呼出し、追加した圧縮情報を設定します。寿分割パラメータに追加した単一ファイル指定拡張子スキップSkipAtExtension、スキップ拡張子リストSkipExtensions、自動的解凍UncompressExtractは、初期化ファイルから読み込み設定画面で設定し、分割時に参照して、初期化ファイルに書きこみます。つまりメイン画面上には出てきません。しかし、圧縮可不可CompressEnabledはメイン画面で設定します。従って設定画面呼出し前に寿分割パラメータに登録し、設定画面が参照します。

改造SetupMNUClick関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::SetupMNUClick(TObject *Sender)
{
        :
    KotoParam.ToolBarVisible   = ToolBAR->Visible;      // ツールバー表示状態設定
    KotoParam.StatusBarVisible = StatusBAR->Visible;    // ステータスバー表示状態設定
    KotoParam.CompressEnabled  = CompressMNU->Enabled;  // 分割可不可(設定画面は参照のみ)
    if (SetupDLG->Execute(this, &KotoParam)){           // 設定実行→OKボタンが押されたら
        :
}
//---------------------------------------------------------------------------


圧縮情報を初期化ファイルに更新

最後に圧縮情報を初期化ファイルに更新します。同時に、 を行います。

改造FormCloseQuery関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::FormCloseQuery(TObject *Sender, bool &CanClose)
{
    ClearCompressedFile();                       // 圧縮済みファイルクリア
    Application->HelpCommand(HELP_QUIT, 0);      // ヘルプを閉じる

    AnsiString IniFilePath = ExtractFilePath(Application->ExeName) + dfIniFileName;  // 初期化ファイル名作成
    TIniFile *IniFile = new TIniFile(IniFilePath);    // 初期化ファイル取得
    WriteIniFile(IniFile);      // 設定状態保存
    delete IniFile;             // 初期化ファイル終了
    ClearCutSizes(KotoParam.CutSizes);  // 分割サイズリストクリア
    delete KotoParam.SkipExtensions;    // スキップ拡張子リスト破棄
    delete KotoParam.CutSizes ;         // 分割サイズリスト破棄
}
//---------------------------------------------------------------------------

改造WriteIniFile関数

分割情報の更新を追加します。
//---------------------------------------------------------------------------
void __fastcall TMainWND::WriteIniFile(TIniFile *IniFile)
{
        :
    for (int i=0;iCount;i++){  // 分割情報ループ
        :
    }

    IniFile->WriteBool(CompressSection, "Compress",   CompressMNU->Checked);                        // 分割元ファイル圧縮設定更新
    IniFile->WriteBool(CompressSection, "SkipAtExtension", KotoParam.SkipAtExtension);              // 単一ファイルのみ圧縮設定更新
    IniFile->WriteString(CompressSection, "SkipExtensions", KotoParam.SkipExtensions->CommaText);   // スキップ拡張子リスト更新
    IniFile->WriteBool(CompressSection, "Uncompress", KotoParam.UncompressExtract);                 // 結合後自動解凍設定更新
}
//---------------------------------------------------------------------------


明らかに自明なWorning

寿分割をビルドすると、今回はじめて警告エラーが残ります。
[C++警告] main.cpp(1030): 'Unlha' is assigned a value that is never used.
これはGetCompress関数でUNLHA32.DLLからUnlha関数のポインタを取得しているにも関わらず、使用していないための「代入した値は使われていない」を意味する警告エラーです。第5話警告(ワーニング)も決して見逃さないと言いました。赤色で強調するほど重要なことです。一見相反しているように見えますが、警告エラーが出る原因が明らかで、それが悪い影響を及ぼさないのであれば、あまり警告をなくす努力は無駄です。
この場合は、UNLHA32.DLLの中にUnlha関数があるかどうかで圧縮が可能かどうかを判断しています。DLLさせあれば圧縮可能とするとUNLHA32.DLLがかなり古いバージョンの場合に正しく動作しない可能性もあります。Unlha関数の存在を確認する処理ですから、上記の警告エラーが出るのは明らかであり、全く問題はありません。


今回のバグ修正

リムーバブルメディア交換を中断した場合、分割が完了しているファイルの状態が「未出力」となっていました。分割が完了していない場合は構いませんが、分割終了後のメディア交換時なら「出力済」です。第16話で不具合を修正した際に十分ではなかったようです。出力が完了してる場合(DividePRG->Position==100)はステータス表示は行いません。

改造OnDivideTRDTerminate関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::OnDivideTRDTerminate(TObject *Sender)
{
        :

    if (ListVEW->Selected!=NULL){                   // 分割ファイル一覧が選択されていたら
        if (DividePRG->Position<100){                   // 分割が完了していない場合
            if (DivideTRD->GetDividedStatus()<0) ListVEW->Selected->SubItems->Strings[1] = "未出力";
        }
    }

        :
}
//---------------------------------------------------------------------------


以上で圧縮分割機能の追加は完了です。最近私はフロッピーディスクを使いません。QuickDriveと言うUSB接続の小さな不揮発性RAMメディアを主に転送用や一時保管用に使っています。空き容量はおよそ30Mあります。寿分割で必要なデータをフォルダごと圧縮し、空き容量全てで分割すると100M程度のデータでもちょちょいと転送できてしまいます。会社のネットワークに個人持ちのPCを繋ぎたくないので、大きなデータの転送にはもってこいです。(^_^)/
こちらから今回作成したBCBのプロジェクト一式をダウンロードできます(ヘルプ一式は含みません)。プロジェクトダウンロード(54,691バイト) BCBのバージョン3、4、5でビルドできます。BCBバージョン5ではの修正を行ってください。
ヘルププロジェクト一式のダウンロードはこちら。ヘルプダウンロード(22,791バイト) 参考にしてください。

次回は分割メール送信機能を追加します。MAPIを活用して寿分割から直接メールを送ります。おそろしく奥が深いです。ご覚悟めされよ。m(_ _)m

今回のプログラミング行数: 367行
今回までのプログラミング行数: 2,181行


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