目录
第1章简介
zlib是一个用于解压缩的开源C函数库。很多项目(如libpng)会用到它。
它的下载网址如下:
http://sourceforge.net/projects/libpng/files/zlib/
本文对zlib自带的Visual C++项目进行解析。
第2章版本1.2.3
2.1 编译汇编代码
版本1.2.3里,为了提高解压缩效率,函数inflate_fast和longest_match用汇编代码实现了。这两个函数有三个版本,如下表所示:
函数 |
C |
ASM x86 |
ASM x64 |
inflate_fast |
inffast.c |
inffas32.asm |
inffas8664.c inffasx64.asm |
longest_match |
deflate.c |
gvmat32c.c gvmat32.asm |
gvmat64.asm |
具体情况为:
函数inflate_fast在inffast.c里有一份实现它的C代码;在inffas32.asm里有一份实现它的32位汇编代码;在inffas8664.c里会调用函数inffas8664fnc,而后者在文件inffasx64.asm里由64位汇编代码实现。
函数longest_match在deflate.c里有一份实现它的C代码;在gvmat32c.c里会调用函数longest_match_7fff和longest_match_686,而这两个函数在文件gvmat32.asm里由32位汇编代码实现;在gvmat64.asm里有一份实现它的64位汇编代码。
简而言之就是:
不用汇编代码,需要编译inffast.c和deflate.c;
使用32位汇编代码,需要编译inffas32.asm、gvmat32c.c、gvmat32.asm;
使用64位汇编代码,需要编译inffas8664.c、inffasx64.asm、gvmat64.asm。
*.c文件在Visual C++编译时会自行处理,*.asm文件就需要特别处理了。
2.1.1 32位汇编
contrib\masmx86目录下的inffas32.asm和gvmat32.asm是32位的汇编代码。编译后可运行在32位的Windows上,也可运行在兼容x86指令的64位Windows上。不能运行在不兼容x86指令的64位Windows上(如:CPU为Itanium的Windows)。
进入contrib\masmx86目录,运行bld_ml32.bat即可编译inffas32.asm和gvmat32.asm。文件bld_ml32.bat的内容如下:
ml /coff /Zi /c /Flgvmat32.lst gvmat32.asm ml /coff /Zi /c /Flinffas32.lst inffas32.asm |
它的含义是调用ml.exe,将gvmat32.asm、inffas32.asm编译生成gvmat32.obj、inffas32.obj。
ml.exe是微软的宏汇编程序,安装了VC++2002至VC++2015后ml.exe也同时被安装,它所在的目录如下表所示:
vc版本 |
目录 |
其它 |
2002 |
C:\Program Files\Microsoft Visual Studio .NET\Vc7\bin |
|
2003 |
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin |
|
2005 |
C:\Program Files\Microsoft Visual Studio 8\VC\bin |
amd64\ml64.exe x86_amd64\ml64.exe |
2008 |
C:\Program Files\Microsoft Visual Studio 9.0\VC\bin |
amd64\ml64.exe x86_amd64\ml64.exe |
2010 |
C:\Program Files\Microsoft Visual Studio 10.0\VC\bin |
amd64\ml64.exe x86_amd64\ml64.exe |
2012 |
C:\Program Files\Microsoft Visual Studio 11.0\VC\bin |
amd64\ml64.exe x86_amd64\ml64.exe |
2013 |
C:\Program Files\Microsoft Visual Studio 12.0\VC\bin |
amd64_x86\ml.exe amd64\ml64.exe x86_amd64\ml64.exe |
2015 |
C:\Program Files\Microsoft Visual Studio 14.0\VC\bin |
amd64_x86\ml.exe amd64\ml64.exe x86_amd64\ml64.exe |
说明:
1、从vc2005开始提供了ml64.exe,它用来编译生成64位的汇编代码;
2、从vc2013开始又提供了amd64_x86\ml.exe;
3、两个ml.exe和两个ml64.exe的区别:
ml.exe 是32位程序,编译生成32位的汇编代码;
amd64_x86\ml.exe 是64位程序,编译生成32位的汇编代码;
amd64\ml64.exe 是64位程序,编译生成64位的汇编代码;
x86_amd64\ml64.exe 是32位程序,编译生成64位的汇编代码。
上述文件夹命名都是有含义的:amd64_x86前面的amd64表示ml.exe是64位的,后面的x86表示编译生成的汇编代码是32位的;amd64表示amd64_amd64,即ml.exe和生成的汇编代码都是64位的。
为了能够正常编译,有两种方法:
方法一:将ml.exe从相应的目录中复制到bld_ml32.bat所在目录,运行bld_ml32.bat即可;
方法二:在bld_ml32.bat所在目录创建vc2002.bat,其内容如下:
%VSCOMNTOOLS%\..\..\Vc7\bin\ml /coff /Zi /c /Flgvmat32.lst gvmat32.asm %VSCOMNTOOLS%\..\..\Vc7\bin\ml /coff /Zi /c /Flinffas32.lst inffas32.asm |
现在运行vc2002.bat即可调用C:\Program Files\Microsoft Visual Studio .NET\Vc7\bin\ml.exe进行编译。
VSCOMNTOOLS是一个环境变量,安装vc2002后它被自动添加到系统里,如下图所示:
同理,还可以创建vc2003.bat
"%VS71COMNTOOLS%..\..\Vc7\bin\ml" /coff /Zi /c /Flgvmat32.lst gvmat32.asm "%VS71COMNTOOLS%..\..\Vc7\bin\ml" /coff /Zi /c /Flinffas32.lst inffas32.asm |
还可以创建vc2005.bat
"%VS80COMNTOOLS%..\..\VC\bin\ml" /coff /Zi /c /Flgvmat32.lst gvmat32.asm "%VS80COMNTOOLS%..\..\VC\bin\ml" /coff /Zi /c /Flinffas32.lst inffas32.asm |
经实际测试,vc2002.bat和vc2003.bat能够正常运行,而vc2005.bat在编译时出错。也就是说ml.exe只能使用vc2002和vc2003附带的版本,再高的版本就不行了。
2.1.2 64位汇编
contrib\masmx64目录下的gvmat64.asm和inffasx64.asm是64位的汇编代码。编译后可运行在64位的Windows上(CPU为Itanium的Windows例外)。
进入contrib\masmx64目录,运行bld_ml64.bat即可编译gvmat64.asm和inffasx64.asm。文件bld_ml64.bat的内容如下:
ml64.exe /Flinffasx64 /c /Zi inffasx64.asm ml64.exe /Flgvmat64 /c /Zi gvmat64.asm |
它的含义是调用ml64.exe,将gvmat64.asm、inffasx64.asm编译生成gvmat64.obj、inffasx64.obj。
为了能够正常编译,有两种方法:
方法一:将ml64.exe从C:\Program Files\Microsoft Visual Studio 8\VC\bin\x86_amd64复制到bld_ml64.bat所在目录,运行bld_ml64.bat即可;
方法二:在bld_ml64.bat所在目录创建vc2005.bat,其内容如下:
"%VS80COMNTOOLS%..\..\VC\bin\x86_amd64\ml64.exe" /Flinffasx64 /c /Zi inffasx64.asm "%VS80COMNTOOLS%..\..\VC\bin\x86_amd64\ml64.exe" /Flgvmat64 /c /Zi gvmat64.asm |
现在运行vc2005.bat即可进行编译。
2.2 Visual C++ 6.0
使用Visual C++6.0打开projects\visualc6\zlib.dsw,如下图所示。可以看到一共有三个项目:example、minigzip、zlib。前两个项目不用理会,鼠标右键单击zlib,然后单击【Set as Active Project】,设置zlib为活动项目。
2.2.1 编译配置项
项目zlib一共有8个编译配置项,如下图所示:
"Win32 DLL*"表示编译生成动态库;
"Win32 LIB*"表示编译生成静态库;
"*Release"表示编译生成Release版,发布程序时使用此版本;
"*Debug"表示编译生成Debug版,调试程序时使用此版本;
含有ASM的表示编译时使用汇编代码;
未含ASM的表示编译时不使用汇编代码。
2.2.2 宏ASMV和ASMINF
查看zlib的配置,当选中含有ASM的编译配置项时,可以看到宏ASMV和ASMINF被定义了。如下图所示:
宏ASMINF被定义,则inflate_fast的C代码被禁用,以下代码节选自inffast.c
#ifndef ASMINF void inflate_fast(strm, start) z_streamp strm; unsigned start; { ... ... ... } #endif /* !ASMINF */ |
宏ASMV被定义,则longest_match的C代码被禁用,以下代码节选自deflate.c
#ifndef ASMV local uInt longest_match(s, cur_match) deflate_state *s; IPos cur_match; { ... ... ... } #endif /* ASMV */ |
也就是说:定义宏ASMINF的含义是启用inflate_fast的汇编代码;定义宏ASMV的含义是启用longest_match的汇编代码。
2.2.3 排除编译
Visual C++6.0无法编译64位的汇编代码,因此不用管inffas8664.c、inffasx64.asm、gvmat64.asm这三个文件。
zlib项目里含有与32位汇编相关的三个文件:gvmat32.asm、gvmat32c.c、inffas32.asm。
当选中不含ASM的编译配置项时,可以看到这三个文件在编译时是被排除在外的,如下图所示:
当选中含有ASM的编译配置项时,可以看到这三个文件将参与编译,如下图所示:
2.2.4 自定义编译
当选中含有ASM的编译配置项时,可以看到gvmat32.asm、inffas32.asm是如何被编译的。如下图所示:
命令如下所示:
ml.exe /nologo /c /coff /Cx /Zi /Fo"$(IntDir)\$(InputName).obj" "$(InputPath)" |
因为VC++6.0并不自带ml.exe,因此上述命令执行时将会失败。解决方法:将C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin\ml.exe复制到C:\Program Files\Microsoft Visual Studio\VC98\Bin。即将vc2002的ml.exe复制到vc6的安装目录。
2.3 Visual C++ 2002
使用vc2002打开contrib\vstudio\vc7\zlibvc.sln,如下图所示。可以看到一共有五个项目:miniunz、minizip、testZlibDll、zlibstat、zlibvc。前三个项目不用理会,zlibstat、zlibvc分别是zlib的静态库、动态库项目。这里只关心zlibvc项目,鼠标右键单击zlibvc,然后单击【Set as StartUp Project】,设置zlibvc为活动项目。
2.3.1 编译配置项
项目zlib一共有5个编译配置项,如下图所示:
"Debug"编译生成Debug版,调试程序时使用此版本;
"Release"编译生成Release版,使用汇编代码;
"ReleaseAxp"编译生成Release版,不使用汇编代码;
"ReleaseWithoutAsm"编译生成Release版,不使用汇编代码。它似乎与ReleaseAxp没什么区别;
"ReleaseWithoutCrtdll"编译生成Release版,使用汇编代码。
Release、ReleaseAxp、ReleaseWithoutAsm使用的C运行时库文件不是msvcr70.dll,而是crtdll.dll。笔者对此的理解是:crtdll.dll比msvcr70.dll更加普及。
ReleaseWithoutCrtdll使用的C运行时库文件是msvcr70.dll不再是crtdll.dll。
虽然crtdll.dll比msvcr70.dll更加普及,但是crtdll.lib很难找到了。没有crtdll.lib的情况下,Release、ReleaseAxp、ReleaseWithoutAsm无法完成链接。
2.3.2 宏ASMV和ASMINF
使用汇编代码的配置项,均定义了宏ASMV和ASMINF,如下图所示
2.3.3 排除编译
使用汇编代码的配置项,gvmat32c.c被排除编译。如下图所示:
未使用汇编代码的配置项,gvmat32c.c未被排除编译。如下图所示:
2.3.4 嵌入汇编obj文件
与VC++6.0不同,VC2002里不再自定义编译asm文件,而是直接使用asm文件编译后的obj文件。
下图是配置项"ReleaseWithoutCrtdll"的Linker、Input选项。可以看到它将使用asm文件的编译结果文件:gvmat32.obj、inffas32.obj。
2.4 Visual C++ 2005
使用vc2005打开contrib\vstudio\vc8\zlibvc.sln,如下图所示。可以看到一共有六个项目。前四个项目不用理会,zlibstat、zlibvc分别是zlib的静态库、动态库项目。这里只关心zlibvc项目,鼠标右键单击zlibvc,然后单击【Set as StartUp Project】,设置zlibvc为活动项目。
2.4.1 编译配置项
项目zlib一共有3个编译平台,如下图所示:
Win32 编译出32位程序,可运行在32位和64位Windows上;
x64 编译出64位程序,可运行在64位Windows上;
Itanium编译出64位程序,只能运行在CPU为Itanium的Windows上。
每个编译平台又有3个配置项,如下图所示:
"Debug"编译生成Debug版,调试程序时使用此版本;
"Release"编译生成Release版,使用汇编代码;
"ReleaseWithoutAsm"编译生成Release版,不使用汇编代码。
2.4.2 宏ASMV和ASMINF
使用汇编代码的配置项,均定义了宏ASMV和ASMINF,如下图所示
2.4.3 排除编译
gvmat32c.c和inffas8664.c可能被排除编译。如下图所示:
编译平台为Win32时,inffas8664.c始终被排除编译,gvmat32c.c视是否使用汇编代码而定;
编译平台为x64时,gvmat32c.c始终被排除编译,inffas8664.c视是否使用汇编代码而定;
编译平台为Itanium时,gvmat32c.c和inffas8664.c始终被排除编译。因为这个编译平台的汇编代码压根没有实现。
2.4.4 嵌入汇编obj文件
与VC2002相同,VC2005也不再自定义编译asm文件,而是直接使用asm文件编译后的obj文件。
需要注意的是:Win32和x64平台使用的obj文件是不同的。下图是x64平台下,使用masmx64目录下的两个obj文件。
2.5 宏ZLIB_WINAPI
定义了ZLIB_WINAPI,则zlib导出的函数其调用约定全部被设置为WINAPI,即__stdcall。这么做的好处是:不是C语言的客户程序,如VB6.0也可以使用zlib动态库了。
VC++6.0的宏定义里没有发现ZLIB_WINAPI,也就是说VC++6.0编译生成的zlib动态库,其导出函数的调用约定不是__stdcall,而是__cdecl。
VC2002和VC2005的宏定义里均有ZLIB_WINAPI,编译生成zlib动态库后,其导出函数的调用约定是__stdcall。
客户端使用zlib动态库时,应该也要定义ZLIB_WINAPI,具体代码如下:
#define ZLIB_WINAPI #include "zlib.h" |
遗憾的是,至少在libpng1.4.12项目里笔者并没有发现#include "zlib.h"之前有#define ZLIB_WINAPI。
笔者认为比较好的方法应该是把#define ZLIB_WINAPI直接添加到zlib.h里,保证编译生成zlib动态库、客户端调用zlib时zlib的导出函数均是__stdcall的。
第3章版本1.2.8
3.1 编译汇编代码
与版本1.2.3相比,版本1.2.8的longest_match函数的32位汇编代码由gvmat32c.c、gvmat32.asm两个文件合并为match686.asm这一个文件。具体如下表所示:
函数 |
C |
ASM x86 |
ASM x64 |
inflate_fast |
inffast.c |
inffas32.asm |
inffas8664.c inffasx64.asm |
longest_match |
deflate.c |
match686.asm |
gvmat64.asm |
现在的情况变为:
不用汇编代码,需要编译inffast.c和deflate.c;
使用32位汇编代码,需要编译inffas32.asm、match686.asm;
使用64位汇编代码,需要编译inffas8664.c、inffasx64.asm、gvmat64.asm。
3.1.1 编译
编译32位汇编代码:将ml.exe从C:\Program Files\Microsoft Visual Studio 8\VC\bin\复制到contrib\masmx86,运行bld_ml32.bat即可。
编译64位汇编代码:将ml64.exe从C:\Program Files\Microsoft Visual Studio 8\VC\bin\x86_amd64复制到contrib\masmx64,运行bld_ml64.bat即可。
当然,使用vc2008、vc2010、vc2012、vc2013、vc2015的ml.exe、ml64.exe应该也是可以的。
3.2 使用Visual C++编译
进入contrib\vstudio目录,可以发现vc9、vc10、vc11三个子目录,分别用vc2008、vc2010、vc2012编译。设置与上一章大致相同,这里就不赘述了。需要特别说明的是vc2010里的"编译前事件"。
3.2.1 编译前事件
vc2010里有"编译前事件",如下图所示:
其含义为:编译代码之前将执行"编译前事件",上图的命令如下所示:
cd ..\..\masmx64 bld_ml64.bat |
其含义就是设置contrib\masmx64为当前目录,然后执行bld_ml64.bat。
..\..\masmx64是相对路径,相对于项目文件(contrib\vstudio\vc10\zlibvc.vcxproj)的相对路径,即contrib\vstudio\vc10\..\..\masmx64,也就是contrib\masmx64。
使用vc2010编译zlib代码,不用费劲找ml.exe和ml64.exe了,也不用考虑应该使用哪个版本的ml.exe和ml64.exe了。一切方便了很多。