ZIP文件是一种很常见的压缩文件格式,用户在windows下经常要使用WINZIP程序进行文件压缩和解压操作。不过,WINZIP程序只能由用户操作,而没有提供开发方面的接口。这样,要想在用户的应用程序中加入文件压缩和解压功能,就有一定困难了。幸好,有ZLIB这个开放源代码的压缩和解压库可供开发人员使用。不过,ZLIB虽然支持文件压缩和解压,但只能对Linux/Unix下的GZ文件进行读写操作,对于Windows系统下的ZIP文件并不提供直接的支持。要解决ZLIB不能直接操作Windows ZIP文件的矛盾,就需要对Windows下ZIP文件结构进行分析,再结合ZLIB所提供的相关函数,加上一些开发技巧,自行开发相应的Winzip程序。
一、ZIP文件结构 ZIP文件基本结构如下:
{分文件头信息+文件压缩数据}+中心目录+中心目录记录结束符
更详细的说明如下:
每个分文件头信息后面紧跟此文件压缩数据。如果压缩方式是不压缩,就是该文件的从第1个字节一直到最后一个字节的原始字节流;如果压缩方式是deflate,压缩数据就是经过deflate算法压缩过的字节流。
ZIP文件中通常有若干个分文件数据,最多可达到65535个。
文件的最后修改时间和日期按MS-DOS时间日期格式编码。时间和日期均为16位整数。
对于时间,16位格式分配如下:
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
小时 |
分 |
秒 |
对于日期,16位格式分配如下:
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
年-1980 |
月 |
日 |
ZIP文件使用32位CRC校验码检查数据是否有误。
ZIP文件中单个文件和ZIP文件本身字节数不能大于4G,否则会出错。
文件名实际长度由文件名长这个字段指定,文件名中可以包含路径,但不包含驱动器盘符(要特别注意的是,所有路径分割符’\’都要转换为’/’,并且路径中第一个字符不能是’/’)。
外部文件属性这个字段,最低1个字节是文件的DOS属性字节,其它字节均设为0)。其中DOS属性字节格式如下:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
未用 |
未用 |
档案 |
目录 |
卷标 |
系统 |
隐藏 |
只读 |
分文件头相对位移和中心目录初始偏移都是按字节表示的相对于ZIP文件开始位置的偏移量,用于在ZIP文件中准确定位。
中心目录中每个文件都可以有注释,用户可以将需要保存的额外信息保存在该字段中。
二、ZLIB使用说明
ZLIB的作者是文件压缩方面的专家,通过一系列复杂的算法实现了deflate这种格式的压缩与解压,并且为开发人员提供了相对简单的函数接口。
ZLIB流格式在RFC1950中有详细的定义。对于ZLIB流,除去流中开头的2个字节和结尾的4个字节,中间的连续字节流即经过deflate转换的压缩流。
ZLIB是开放源代码的,可以从www.zlib.net上下载源代码包,也可以下载编译好的Windows系统下的DLL。目前最新的ZLIB版本为1.2.3,dll文件名为zlib1.dll,头文件名为zlib.h,还需要用一个zconf.h。
本程序一共使用了ZLIB提供的6个函数,其中压缩函数3个,解压函数3个。
压缩要用到deflateInit、deflate、deflateEnd这3个函数,解压要用到inflateInit,inflate,inflateEnd这3个函数。
deflateInit在压缩开始之间调用,压缩结束后调用deflateEnd;同样,解压之前调用inflateInit,解压完成后调用inflateEnd。这4个函数都很好理解。
关键函数是deflate进行压缩,inflate进行解压。
deflate函数有两个参数,stream和flush。stream是一个结构体变量,有next_in、avail_in、next_out、avail_out这四个变量。next_in表示当前输入的字节数组,avail_in表示当前可用的输入字节数;next_out表示当前输出的字节数组,avail_out表示当前可用的输出字节数。当输入数据没有结束时flush设为Z_NO_FLUSH,否则设为Z_FINISH。next_out要至少比next_in大0.0015%。
inflate函数参数和deflate相同,但flush总是设为Z_NO_FLUSH。
为压缩整个文件,应当循环调用deflate函数进行数据压缩。同样,也要循环调用inflate函数进行解压。当avail_out这个变量为0时,表示输出缓冲已满,这时需要将next_out中数据写入文件,写入字节数由next_out大小-avail_out决定。然后重新调用deflate进行压缩或inflate进行解压。
三、程序开发过程
本程序使用Visual C++ 6.0开发。使用MFC AppWizard生成基于对话框的应用程序框架。
主对话框截图如图1所示。
图1 主界面
主对话框中加入菜单,包括文件和动作两个子菜单,其中文件菜单中只有一个打开菜单项,用于打开ZIP文件。动作菜单有加入、删除、解出三个菜单项。
菜单下面是一个MSFlexGrid控件,用于显示ZIP文件中的文件。
主对话框底部有三个标签,用于显示有关信息。
用户单击文件菜单的打开命令,会出现打开文件对话框,用于打开或新建ZIP文件。
用户单击动作菜单中的加入命令,会出现选择文件对话框,由用户选择要加入的文件。
选择文件对话框截图如2所示。
图2选择文件对话框
该对话框中,压缩和保存路径两个复选按钮默认都是选中状态,即进行压缩和保存文件路径,用户可以改变这个设置。压缩复选按钮左边是一个下拉列表框,由用户选择驱动器,上面的MSFlexGrid列表用于显示当前目录和文件,用户在该列表按空格键选择文件,选中文件将出现在对话框下面的列表框中,单击确定按钮则进行将选择文件加入ZIP文件。
用户单击动作菜单的解压按钮,会出现解压对话框,由用户输入解压路径。
解出对话框截图如下:
用户只要输入一个解压目录,并单击确定按钮,就会将ZIP文件中文件解压至该目录。
源文件中,有4个包含开发时加入的源代码:winzip.cpp中定义一些全局变量;selectfiledlg.cpp为选择文件对话框类;extractdlg.cpp为输入解压路径对话框类;winzipdlg.cpp为主对话框类,其中包含主要的源代码;其它文件均由MFC自动生成。
在winzip.cpp中定义的全局变量如下:
CWinzipApp *theApp=new CWinzipApp;
//应用程序指针,AppWizard产生的是一个变量,要将其改成指针,否则编译会报错
CMenu *menu1=new CMenu; //用户制作的菜单
CString *zipfilepath=new CString; //用于标识带路径的zip文件名
unsigned long color1,color2,color3; //color1表示表格前景颜色;color2表示表格背景颜色;color3表示选中表格背景颜色
unsigned short int compressmode=0x08; //用于标识压缩方法 0x08-deflate压缩; 0x00-不压缩
unsigned short int savepath=1; //用于标识是否保存路径 1-保存路径 0-不保存路径
CString *statustext=new CString; //显示在提示栏中的文字
CString *offsetstr=new CString; //用于存放zip文件中文件的偏移地址
unsigned short int selectcount; //标识选中文件个数
CString *extractpath=new CString; //标识解压文件路径
CString *packsizestr=new CString; //标识解压文件大小
CString *filenamestr=new CString; //标识解压文件名称
CString *defaultpath=new CString; //标识默认zip文件所在路径
CString *addfilenamestr=new CString; //标识要加入文件名称
int addflag=0; //加入标志 0-不加入;1-加入
selectfiledlg.cpp从Cdialog派生一个对话框类,用于压缩文件时选择要加入的文件。
selectfiledlg.cpp中有以下几个函数:
(1)void FindAllFiles(CString dirname,CListBox &list); //查找dirname目录下及其子目录中所有文件,结果放在一列表控件中
(2)int deletefiles(CString zipfile); //从ZIP文件中删除文件
(3)UINT do_zipfiles(LPVOID pParam); /*压缩文件线程,实际先从zip文件中删除相同文件*/
(4)BOOL Cselectfiledlg::OnInitDialog(); /*初始化文件选择对话框*/
(5)void Cselectfiledlg::OnSelchangeCombo1(); /*用户选择驱动器后更新该驱动器下目录*/
(6)void Cselectfiledlg::OnDblClickMsflexgrid1(); /*用户双击目录列表进入相应目录,并显示目录下文件和子目录*/
(7)void Cselectfiledlg::OnKeyDownMsflexgrid1(short FAR* KeyCode, short Shift); /*用户使用空格键选中目录列表中文件,选中文件放入最下面列表框中*/
(8)void Cselectfiledlg::OnLButtonDown(UINT nFlags, CPoint point); /*按鼠标左键则取消所有表格控件中选中的文件*/
(9)void Cselectfiledlg::OnButton1(); /*用户单击确定按钮开始进行压缩*/
extractdlg.cpp中有以下几个函数:
(1)UINT do_unzipfiles(LPVOID pParam); /*定义解压文件线程*/
(2)void Cextractdlg::OnButton1(); /*用户按确定按钮进行解压操作*/
winzipdlg.cpp为主对话框源文件,其中包括了本程序最核心部分的代码,下面进行详细描述:
程序中加入菜单,资源ID为IDR_MENU1,包括文件和操作两个子菜单,其中文件菜单中只有一个打开菜单项,用于打开ZIP文件。操作菜单有加入、删除、解出三个菜单项。
在主对话框的OnInitDialog事件中加入以下两行代码就可正常使用菜单了。
menu1->LoadMenu(IDR_MENU1);
SetMenu(menu1);
Winzipdlg.cpp中有如下函数:
(1)void lowerstr(char *str); /*将输入字符串转换为小写*/
(2)void FindAllFiles(CString dirname,CListBox &list); /*在dirname中找文件,结果放入list中,通过递归查找子目录中文件*/
(3)int convdrive(char *drive); /*将驱动器字母转换为数字*/
(4)unsigned long Reflect(unsigned long int ref, char ch); /*crc内部用函数*/
(5)void init_crc32_table(void); //初始化crc32表
(6)unsigned long GenerateCRC32(char * DataBuf,unsigned long len); /*对DataBuf中长度为len的串取CRC码*/
(7)unsigned long FileGenerateCRC32(CString filename); /*对filename文件取CRC码*/
(8)unsigned short int istextfile(CString filename);/*判断filename类型
返回值:1-textfile 0-binaryfile -1-无此文件*/
(9)int makedir(char *drive,char *path); /*在drive驱动器和path目录下建立目录,path开头和结尾都要有'\'*/
(10)int deletefiles(CString zipfile); /*从zipfile中删除选中文件*/
(11)int zipfiles(CString zipfile); /*向ZIP文件中加入文件*/
(12)int unzipfiles(CString zipfile); /*从ZIP文件中解压文件*/
(13)UINT do_zipfiles1(LPVOID pParam); /*加入文件线程*/
(14)BOOL CWinzipDlg::OnInitDialog();/*初始化主对框,并进行CRC表初始化*/
(15)void CWinzipDlg::OnClose(); /*关闭主对话框*/
(16)void CWinzipDlg::OnSize(UINT nType, int cx, int cy); /*当用户改变窗口大小时相应改变空间大小*/
(17)void CWinzipDlg::OnOpen(); /*用户打开zip文件*/
(18)void CWinzipDlg::OnExtract(); /*用户从zip文件解出文件*/
(19)UINT do_deletefiles(LPVOID pParam); /*从zip文件删除文件线程*/
(20)void CWinzipDlg::OnDelete(); /*用户从zip文件删除文件*/
(21)void CWinzipDlg::OnAdd(); /*用户向zip文件加入文件*/
(22)BOOL CWinzipDlg::PreTranslateMessage(MSG* pMsg); /*处理用户定义消息*/
(23)void CWinzipDlg::OnKeyDownMsflexgrid1(short FAR* KeyCode, short Shift); /*用户按空格键选中或取消ZIP文件列表中文件*/
(24)void CWinzipDlg::OnLButtonDown(UINT nFlags, CPoint point); /*按鼠标左键取消表格控件选中文件*/
主对话框中加入MSFlexGrid X控件,用于显示ZIP文件目录。
程序要使用zlib1.dll这个动态链接库,将deflateInit、deflate、deflateEnd、inflateInit、inflate、inflateEnd这6个函数引用到用户程序后使用。
为避免压缩文件数量众多或文件太大时应用程序失去用户响应,使用工作线程进行压缩、解压、删除操作,通过全局变量传递所用参数,使用消息发送机制输出执行结果。
选择文件时使用递归查找目录下子目录中所有文件;其它还包括CRC码生成;判定文件是否文本文件;在指定路径下循环建立子目录等相关函数;具体代码可参见源程序。
使用6个字节的数组保存ZLIB中开头2个字节和结尾4个字节,这6个字节保存在中心目录的注释字段中。只要能准确地保存和读取ZLIB流中这6个字节,就能够完成ZIP文件压缩和解压功能。
在弄清楚ZIP文件结构基础上,可以较容易地编写实现压缩、解压、删除、和查看这4项ZIP文件中最基本的功能的源代码。下面分别进行详述:
因代码太长,不便全部列出,故只对实现的步骤进行描述,具体代码可参见源程序。
压缩部分操作步骤为:
(1)将zlib1.dll中3个压缩函数引入
(2)打开ZIP文件fp1用于读
(3)打开一个不带扩展名的ZIP文件fp2用于读写
(4)从fp1中取中心目录初始偏移
(5)将fp1中开始至中心目录前数据全部拷入fp2中(这部分是zip文件中原来的分文件数据)
(6)将要加入文件逐个写入fp2中(首先写入分文件头信息,然后是分文件头数据,如果压缩方式是不压缩,则直接写入,如果压缩方式是deflate压缩,则调用deflate函数进行压缩后写入)
(7)将原ZIP文件中心目录写入fp2
(8)逐个将新加入文件中心目录写入fp2(注意如压缩方式为deflate,则要向该文件注释中写入zlib流中头两个字节和末尾4个字节,以便于以后解压缩)
(9)更新fp2的中心目录记录结束符
(10)关闭fp1,fp2,并将fp2改名为原ZIP文件
解压操作步骤为:
(1)将zlib1.dll中3个解压函数引入
(2)建立解压目录
(3)打开ZIP文件fp1用于读
(4)逐个从ZIP文件中心目录中取要解出文件名称,偏移量等信息,将该文件从ZIP文件中提取出来,并写到解压路径下(如压缩方式为不压缩,则直接读取并写入,如压缩方式为deflate,,则调用inflate函数解压后再写入,该文件注释中有zlib流中开始2个字节和末尾两个字节,结合文件注释将zlib流拼完整就可顺利解压,文件写完后还要设置文件属性和进行CRC校验)
(5)关闭fp1
删除部分操作步骤为:
(1)打开ZIP文件fp1用于读
(2)打开不带扩展名的ZIP文件fp2用于写
(3)逐个查找fp1中心目录,如果发现该文件不是要删除的文件,则将文件fp1指针移到该分文件偏移处,将该分文件数据写入fp2,如果发现该文件是要删除的文件,则继续查找fp1中心目录的下一条记录
(4)更新fp2中心目录
(5)更新fp2中心目录记录结束符
(6)关闭fp1,fp2,将fp2改名为原ZIP文件
查看ZIP文件目录操作步骤为:
(1)打开ZIP文件fp1
(2)找到中心目录,并逐个读取文件目录信息(包括文件名、修改时间、原始大小、压缩后大小、文件属性、文件路径、CRC校验码、相对偏移)。并将读出信息写到主对话框MSFlexGrid控件列表中
(3)关闭fp1
四、结语
经过以上一系列工作,本程序开发成功。本程序可以生成符合ZIP文件结构规范的压缩文件,也可顺利地从ZIP文件中解压文件、从ZIP文件中删除文件、查看ZIP文件目录。程序已通过大量严格测试,验证了在各种可能的输入下输出结果的正确性。
|