實(shí)際應(yīng)用中有時(shí)候會(huì)遇到需要處理 ZIP 壓縮解壓的情況,這時(shí)候我們有大概三種選擇:
- 調(diào)用 rar.exe, unzip.exe 等
- 使用某現(xiàn)成庫(kù)
- 完全手寫
第一種雖然能完成任務(wù),但是沒法知曉結(jié)果。曾經(jīng)有人對(duì)說,可以抓命令行輸出結(jié)果來判斷……這種依靠界面文字來進(jìn)行精確判斷的行為個(gè)人認(rèn)為相當(dāng)不靠譜。第三種,既然我是個(gè)“造輪主義”者,當(dāng)然說好,但是現(xiàn)在我不了解 ZIP 格式,也不了解 ZIP 算法,所以這個(gè)日后再說。今天我們就來切切實(shí)實(shí)地用一次輪子。
ZIP 相關(guān)的庫(kù)中比較有名的可能就是 ZLib 和 InfoZip(unzip60)了。InfoZip 我了解的不多,其外層接口似乎也不大好,一堆回調(diào)——回調(diào)是個(gè)很煩人的東西,專門用來打亂代碼結(jié)構(gòu)。另外,這個(gè)庫(kù)也已經(jīng)有好多年沒更新了吧,太久的東西給人的感覺總是不太舒服。ZLib 最新版本是 1.2.5,今年 4 月 19 日出的。確切的說,ZLib 可能并不是一個(gè)針對(duì) ZIP 文件的庫(kù),它只是一個(gè)針對(duì) gzip 以及 deflate 算法的庫(kù)。它提供了一個(gè)叫做 minizip (contrib\minizip) 例子來給出操作 ZIP 文件的方法。下文將從 ZLib 出發(fā),歸結(jié)出兩個(gè)傻瓜接口:
BOOL ZipCompress(LPCTSTR lpszSourceFiles, LPCTSTR lpszDestFile);
BOOL ZipExtract(LPCTSTR lpszSourceFile, LPCTSTR lpszDestFolder);
要引入的源文件
- ZLib 主目錄下的代碼,除 minigzip.c、example.c 外;
- contrib\minizip 下的代碼,除 minizip.c、miniunz.c 外。
相關(guān) API
雖然 minizip 更像是個(gè)例子,但是除去其主程序 minizip.c 和 miniunz.c 后,剩下的部分我們可以看作是 ZLib 的一個(gè)上層庫(kù),它封裝了與 ZIP 文件格式相關(guān)的操作。而 minizip.c 和 miniunz.c 就是我們要改寫的——把它從命令行程序改為上述傻瓜接口。minizip.c 和 miniunz.c 中用到的 API 主要有:
壓縮相關(guān):
- zipOpen64
- zipClose
- zipOpenNewFileInZip
- zipCloseFileInZip
- zipWriteInFileInZip
解壓相關(guān):
- unzOpen64
- unzClose
- unzGetGlobalInfo64
- unzGoToNextFile
- unzGetCurrentFileInfo64
- unzOpenCurrentFile
- unzCloseCurrentFile
- unzReadCurrentFile
想必看到這些名字都能猜到怎么用了吧。好的接口果然能帶給人愉悅的。minizip 中的這些函數(shù)有的是帶“64”的有的是不帶的,有的還有“2”、“3”、“4”版本。這里一律用帶 64 的,不帶“2”、“3”、“4”的。
具體操作
下文涉及的所有操作,其相關(guān)代碼都可以在 http://zlibwrap.codeplex.com/ 上找到(Change Set 2450)。這里就不貼長(zhǎng)篇代碼了。另外有個(gè) DLL版本 和 Lib版本,供拿來主義者用。
首先是壓縮操作。使用 zipOpen64 來打開/創(chuàng)建一個(gè) ZIP 文件,然后開始遍歷要被放到壓縮包中去的文件。針對(duì)每個(gè)文件,先調(diào)用一次 zipOpenNewFileInZip,然后開始讀原始文件數(shù)據(jù),使用 zipWriteInFileInZip 來寫入到 ZIP 文件中去。zipOpenNewFileInZip 的第三個(gè)參數(shù)是一個(gè) zip_fileinfo 結(jié)構(gòu),該結(jié)構(gòu)數(shù)據(jù)可全部置零,其中 dosDate 可用于填入一個(gè)時(shí)間(LastModificationTime)。它的第二個(gè)參數(shù)是 ZIP 中的文件名,若要保持目錄結(jié)構(gòu),該參數(shù)中可以保留路徑,如 foo/bar.txt。
解壓操作稍微復(fù)雜一點(diǎn)點(diǎn)。打開一個(gè) ZIP 文件后,需要先使用 unzGetGlobalInfo64 來取得該文件的一些信息,來了解這個(gè)壓縮包里一共包含了多少個(gè)文件,等等。目前我們用得著的就是這個(gè)文件數(shù)目。然后開始遍歷 ZIP 中的文件,初始時(shí)自動(dòng)會(huì)定位在第一個(gè)文件,以后處理完一個(gè)后用 unzGoToNextFile 來跳到下一個(gè)文件。對(duì)于每個(gè)內(nèi)部文件,可用 unzGetCurrentFileInfo64 來查內(nèi)部文件名。這個(gè)文件名和剛才 zipOpenNewFileInZip 的第二個(gè)參數(shù)是一樣的形式,所以有可能包含路徑。也有可能會(huì)以路徑分隔符(/)結(jié)尾,表明這是個(gè)目錄項(xiàng)(其實(shí)壓縮操作的時(shí)候也可以針對(duì)目錄寫入這樣的內(nèi)部文件,上面沒有做)。所以接下來要根據(jù)情況創(chuàng)建(多級(jí))目錄。unzGetCurrentFileInfo64 的第三個(gè)參數(shù)是 unz_file_info64 結(jié)構(gòu),其中也有一項(xiàng)包含了 dosDate 信息,可以還原文件時(shí)間。對(duì)于非目錄的內(nèi)部文件,用 unzOpenCurrentFile,打開,然后 unzReadCurrentFile 讀取文件內(nèi)容,寫入到真實(shí)文件中。unzReadCurrentFile 返回 0 表示文件讀取結(jié)束。
局限性
- 只能壓縮、解壓采用 deflate 算法的 ZIP 文件。(不過此類 ZIP 應(yīng)該占了絕大多數(shù))
- 由于 minizip 中相關(guān) API 的限制,以及 ZIP 文件格式的限制,被壓縮/解壓的相關(guān)文件名必須與系統(tǒng)的當(dāng)前代碼頁(yè)相符合。(雖然 ZIP 格式最近一次更新加入了使用 UTF8 編碼文件名的選項(xiàng),但是不能保證所遇到的 ZIP 文件都是新格式的,minizip 中似乎也沒有針對(duì)此選項(xiàng)做什么動(dòng)作。)
尾聲
這是一篇低俗的文章,沒有什么思想性。僅僅是一個(gè)小記。有不當(dāng)之處歡迎批評(píng)指正。
祝大家中秋節(jié)快樂!
posted on 2010-09-22 23:57
溪流 閱讀(46736)
評(píng)論(75) 編輯 收藏 引用 所屬分類:
C++