使用ZLib 壓縮/解壓縮 zip檔案
實際應用中有時候會遇到需要處理 ZIP 壓縮解壓的情況,這時候我們有大概三種選擇:
- 呼叫 rar.exe, unzip.exe 等
- 使用某現成庫
- 完全手寫
第一種雖然能完成任務,但是沒法知曉結果。曾經有人對說,可以抓命令列輸出結果來判斷……這種依靠介面文字來進行精確判斷的行為個人認為相當不靠譜。第三種,既然我是個“造輪主義”者,當然說好,但是現在我不瞭解 ZIP 格式,也不瞭解 ZIP 演算法,所以這個日後再說。今天我們就來切切實實地用一次輪子。
ZIP 相關的庫中比較有名的可能就是 ZLib 和 InfoZip(unzip60)了。InfoZip 我瞭解的不多,其外層介面似乎也不大好,一堆回撥——回撥是個很煩人的東西,專門用來打亂程式碼結構。另外,這個庫也已經有好多年沒更新了吧,太久的東西給人的感覺總是不太舒服。ZLib 最新版本是 1.2.5,今年 4 月 19 日出的。確切的說,ZLib 可能並不是一個針對 ZIP 檔案的庫,它只是一個針對 gzip 以及 deflate 演算法的庫。它提供了一個叫做 minizip (contrib/minizip) 例子來給出操作 ZIP 檔案的方法。下文將從 ZLib 出發,歸結出兩個傻瓜介面:
BOOL ZipCompress(LPCTSTR lpszSourceFiles, LPCTSTR lpszDestFile);
BOOL ZipExtract(LPCTSTR lpszSourceFile, LPCTSTR lpszDestFolder);
要引入的原始檔
- ZLib 主目錄下的程式碼,除 minigzip.c、example.c 外;
- contrib/minizip 下的程式碼,除 minizip.c、miniunz.c 外。
相關 API
雖然 minizip 更像是個例子,但是除去其主程式 minizip.c 和 miniunz.c 後,剩下的部分我們可以看作是 ZLib 的一個上層庫,它封裝了與 ZIP 檔案格式相關的操作。而 minizip.c 和 miniunz.c 就是我們要改寫的——把它從命令列程式改為上述傻瓜介面。minizip.c 和 miniunz.c 中用到的 API 主要有:
壓縮相關:
- zipOpen64
- zipClose
- zipOpenNewFileInZip
- zipCloseFileInZip
- zipWriteInFileInZip
解壓相關:
- unzOpen64
- unzClose
- unzGetGlobalInfo64
- unzGoToNextFile
- unzGetCurrentFileInfo64
- unzOpenCurrentFile
- unzCloseCurrentFile
- unzReadCurrentFile
想必看到這些名字都能猜到怎麼用了吧。好的介面果然能帶給人愉悅的。minizip 中的這些函式有的是帶“64”的有的是不帶的,有的還有“2”、“3”、“4”版本。這裡一律用帶 64 的,不帶“2”、“3”、“4”的。
具體操作
下文涉及的所有操作,其相關程式碼都可以在 http://zlibwrap.codeplex.com/ 上找到(Change Set 2450)。這裡就不貼長篇程式碼了。另外有個 DLL版本 和 Lib版本,供拿來主義者用。
首先是壓縮操作。使用 zipOpen64 來開啟/建立一個 ZIP 檔案,然後開始遍歷要被放到壓縮包中去的檔案。針對每個檔案,先呼叫一次 zipOpenNewFileInZip,然後開始讀原始檔案資料,使用 zipWriteInFileInZip 來寫入到 ZIP 檔案中去。zipOpenNewFileInZip 的第三個引數是一個 zip_fileinfo 結構,該結構資料可全部置零,其中 dosDate 可用於填入一個時間(LastModificationTime)。它的第二個引數是 ZIP 中的檔名,若要保持目錄結構,該引數中可以保留路徑,如 foo/bar.txt。
解壓操作稍微複雜一點點。開啟一個 ZIP 檔案後,需要先使用 unzGetGlobalInfo64 來取得該檔案的一些資訊,來了解這個壓縮包裡一共包含了多少個檔案,等等。目前我們用得著的就是這個檔案數目。然後開始遍歷 ZIP 中的檔案,初始時自動會定位在第一個檔案,以後處理完一個後用 unzGoToNextFile 來跳到下一個檔案。對於每個內部檔案,可用 unzGetCurrentFileInfo64 來查內部檔名。這個檔名和剛才 zipOpenNewFileInZip 的第二個引數是一樣的形式,所以有可能包含路徑。也有可能會以路徑分隔符(/)結尾,表明這是個目錄項(其實壓縮操作的時候也可以針對目錄寫入這樣的內部檔案,上面沒有做)。所以接下來要根據情況建立(多級)目錄。unzGetCurrentFileInfo64 的第三個引數是 unz_file_info64 結構,其中也有一項包含了 dosDate 資訊,可以還原檔案時間。對於非目錄的內部檔案,用 unzOpenCurrentFile,開啟,然後 unzReadCurrentFile 讀取檔案內容,寫入到真實檔案中。unzReadCurrentFile 返回 0 表示檔案讀取結束。
侷限性
- 只能壓縮、解壓採用 deflate 演算法的 ZIP 檔案。(不過此類 ZIP 應該佔了絕大多數)
- 由於 minizip 中相關 API 的限制,以及 ZIP 檔案格式的限制,被壓縮/解壓的相關檔名必須與系統的當前內碼表相符合。(雖然 ZIP 格式最近一次更新加入了使用 UTF8 編碼檔名的選項,但是不能保證所遇到的 ZIP 檔案都是新格式的,minizip 中似乎也沒有針對此選項做什麼動作。)