2012年9月30日日曜日

ZIP圧縮のプログラム

今回は再びプログラミングの話

ZIP圧縮ルーチンをDLLなどつかわず自力で書きました。
だからその覚書をのこしておきたい。
以下はvisual studio 2010で行いました。

参考にしたのはこれ
http://hp.vector.co.jp/authors/VA016379/cpplib/zlib.htm
http://www.tnksoft.com/reading/zipfile/arczip.php

まず必要なものとしてZLIB(http://www.zlib.net/)があります。
最初にこれをダウンロード(現在zlib127.zip)します。

次に適当なLIBプロジェクトを作成してその中にzlib127.zipを解凍して
できた中身のルートのファイルを(サブフォルダをの中のもの以外)すべて追加

最後にコンパイルしてLIBをを得ます。このときプリコンパイルヘッダをOFFでしないとエラーになります。
ここではこれをzlib.lib(release),zlibd.lib(debug)とします。

次にこれを使用するアプリを作成します。

適当なDLGベースのアプリを作成、ボタンを一個置きます。

次にzlib.libをこのプロジェクトに追加します。
ファイルをプロジェクトにコピー後
プロジェクトの設定で追加してもいいですが
プロジェクトのファイルに
#ifdef _DEBUG
#pragma comment(lib,"zlibd.lib")
#else
#pragma comment(lib,"zlib.lib")
#endif
と書いたほうが、うざくなくて私はすきです。

次にヘッダー類のコピーですが、
zconf.h
zlib.h
をコピーします。
そのまま使ってもいいわけですが、
http://www.tnksoft.com/reading/zipfile/arczip.php
から拝借した
ziputil.cpp
ziputil.h
を経由して使いました。
結局プロジェクトには
#include "ziputil.h"
とかけばいいです。

さらにZIPのファイル情報を管理するMFCクラスを作成しました

class CZipFileInfo : public CObject
{
public:
    CZipFileInfo();
    virtual ~CZipFileInfo();
    CStringA strpath;
    int buflen;
    unsigned char  *buff;
    __time64_t filetime;
};

typedef CTypedPtrArray< CPtrArray, CZipFileInfo* > CZipFileInfoArray;


これでようやく環境が整ったのでコーデイング出来ます。
2関数作成しました
//ZIPファイル検索
void SearchPath(CString strpath,CString strCur,CZipFileInfoArray &  ZipInfoArry){
    CFileFind FileFind;
    BOOL FndEndJug;


    //-----------------------------
    //検索ファイル名文字列を生成
    CString strSearchFile = strpath + _T("\\*.*") ;
   
    //----------------
    //検索実行
    if(!FileFind.FindFile(strSearchFile))
        return;
   
    FndEndJug = TRUE;
    while(FndEndJug){
       
        //-------
        //検索
        FndEndJug = FileFind.FindNextFile();
       
        // "." , ".."を無視
        if(FileFind.IsDots())
            continue;
       
        //検索結果の判定
        if(FileFind.IsDirectory())
        {
            // サブ・ディレクトリ内を検索するための再帰呼び出し
            SearchPath(FileFind.GetFilePath(),strCur,ZipInfoArry);
        }
        else {
            // 検索結果をリスト・ボックスへ出力
            CString fullpath=FileFind.GetFilePath();
            //TRACE(fullpath+L"\n");
            TCHAR RelativePath[MAX_PATH];
            PathRelativePathTo(RelativePath,strCur, FILE_ATTRIBUTE_DIRECTORY,fullpath,FILE_ATTRIBUTE_ARCHIVE);


            //ファイルデータ取り出し
            struct _stat64i32 st0;
            _wstat(fullpath,&st0);
            FILE * fp0=NULL;
            _tfopen_s(&fp0,fullpath,L"rb");
            unsigned char * buff=new unsigned char [st0.st_size];
            fread(buff,1,st0.st_size,fp0);
            fclose(fp0);


            CZipFileInfo * pInfo=new CZipFileInfo;
            pInfo->strpath=RelativePath;
            pInfo->buflen=st0.st_size;
            pInfo->buff=buff;
            pInfo->filetime=st0.st_mtime;
            ZipInfoArry.Add(pInfo);
        }
    }
}

//圧縮
void CreateZip(CString strpath,CString strZip){
    TCHAR chCur[MAX_PATH];
    GetCurrentDirectory(MAX_PATH,chCur);

    //ファイルをサーチ
    CZipFileInfoArray  ZipInfoArry;
    SearchPath(strpath,chCur,ZipInfoArry);
    //ファイル数
    int nLen=ZipInfoArry.GetCount();
    if(nLen<=0)return;
    // ヘッダ情報の作成
    ZipHeader * zipheader=new ZipHeader  [nLen];
    CentralDirHeader * archeader=new CentralDirHeader [nLen];
    memset(zipheader, 0, sizeof(ZipHeader) * nLen);
    memset(archeader, 0, sizeof(CentralDirHeader) * nLen);


    for(int i=0;i<nLen;i++){
        TRACE(ZipInfoArry[i]->strpath+"\n");
        zipheader[i].signature = ZIPSIG_CENTDIR;    // PK0304
        zipheader[i].needver = 20;                    // Deflate圧縮を利用できるVer
        zipheader[i].comptype = 8;                    // Deflate圧縮

        // ファイル時刻を取得
        __time64_t time64=ZipInfoArry[i]->filetime;
        tm nowtm;
        _localtime64_s(&nowtm,&time64);

        zipheader[i].filedate = GetDosDate(nowtm.tm_year + 1900, nowtm.tm_mon+1, nowtm.tm_mday);
        zipheader[i].filetime = GetDosTime(nowtm.tm_hour, nowtm.tm_min, nowtm.tm_sec);

        // 展開時に用いるファイル名の設定
        zipheader[i].fnamelen = ZipInfoArry[i]->strpath.GetLength();
        zipheader[i].filename = ZipInfoArry[i]->strpath.GetBuffer();

        // ファイルサイズの設定
        zipheader[i].uncompsize = ZipInfoArry[i]->buflen;


        archeader[i].signature = ZIPSIG_ARCHIVEFILE;    // PK0102
        archeader[i].madever = 20;



    }

    FILE *fp=NULL;
    _tfopen_s(&fp,strZip, _T("wb"));
    if(fp == NULL){
        printf("ファイルが開けませんでした。");
        return;
    }


    // 圧縮関連変数の初期化
    const uInt tempsize = 16384*0x50;
    Byte * tempbuf=new Byte [tempsize];
    uInt compsize;

    z_stream zs;
    memset(&zs, 0, sizeof(z_stream));

    deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);

    // ファイルの位置を一時的に格納する変数
    unsigned int current;


    // アーカイブデータの書き込み
    InitCRC32();
    for(int i=0;i<nLen;i++){
        // データの位置を格納
        archeader[i].headerpos = ftell(fp);

        // zipヘッダの書き込み
        WriteZipHeader(fp, &zipheader[i]);

        // 現在のファイル位置を一時的に取得
        current = ftell(fp);
   
        // zlib構造体をメモリの開放を行わず再初期化する
        deflateReset(&zs);

        // crc32の初期値は0xFFFFFFFFにする
        zipheader[i].crc32 = CRC32_DEFAULT;

        // データの圧縮と書き込み
        compsize = 0;
        zs.avail_in = zipheader[i].uncompsize;
        zs.next_in = (Bytef*)ZipInfoArry[i]->buff;
        do{
            zs.avail_out = tempsize;
            zs.next_out = tempbuf;

            // CRC32値の更新
            //zipheader[i].crc32 = GetCRC32(zs.next_in, zs.avail_in, zipheader[i].crc32);
            zipheader[i].crc32 = GetCRC32(ZipInfoArry[i]->buff,ZipInfoArry[i]->buflen);

            deflate(&zs, Z_FINISH);

            fwrite(tempbuf, 1, tempsize - zs.avail_out, fp);
        }while(zs.avail_out == 0);


        // 圧縮サイズデータの更新
        zipheader[i].compsize = ftell(fp) - current;

        current = ftell(fp);

        // ファイルデータの書き換え
        fseek(fp, archeader[i].headerpos + 14, SEEK_SET);
        fwrite(&zipheader[i].crc32, 1, sizeof(unsigned int), fp);
        fwrite(&zipheader[i].compsize, 1, sizeof(unsigned int), fp);
        fseek(fp, 0, SEEK_END);


        // ヘッダ内容のコピー
        CopyToCentralDirHeader(&archeader[i], &zipheader[i]);
    }

    // zlibメモリの開放
    deflateEnd(&zs);


    // 中央ディレクトリ情報開始位置の格納
    current = ftell(fp);

    // 中央ディレクトリの書き込み
    for(int i = 0; i < nLen; i++){
        WriteCentralDirHeader(fp, &archeader[i]);
    }

    // 終端ヘッダの書き込み
    EndCentDirHeader endheader;
    memset(&endheader, 0, sizeof(EndCentDirHeader));
    endheader.signature = ZIPSIG_ENDCENTDIR;    // PK0506
    endheader.direntry = nLen;
    endheader.diskdirentry = endheader.direntry;
    endheader.startpos = current;
    endheader.dirsize = ftell(fp) - current;
    WriteEndCentDirHeader(fp, &endheader);

    fclose(fp);
    for(int i=0;i<nLen;i++){
        ZipInfoArry[i]->strpath.ReleaseBuffer();
        delete [] ZipInfoArry[i]->buff;
        delete ZipInfoArry[i];
    }

    delete [] tempbuf;
    delete [] zipheader;
    delete [] archeader;
}

で最後にこれを使う部分をボタンの応答関数に書きます。
void CZIPsampleDlg::OnBnClickedZipRun()
{
    //workのフルパスを作成
    TCHAR cbuff[MAX_PATH];
    ZeroMemory(cbuff,sizeof(cbuff));
    GetCurrentDirectory(MAX_PATH,cbuff);
    lstrcat(cbuff,L"\\work");

    CTime tm=CTime::GetTickCount();
    CString filename=tm.Format("%Y-%m-%d %H-%M.zip");
    CreateZip(cbuff,filename);

}
これでカレントのworkフォルダの中身が現在時刻のファイルに圧縮されます。
ここまで読んでくれた人のためにプロジェクトを添付します。
サンプルプロジェクト

0 件のコメント:

コメントを投稿