目錄
第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了。一切方便了很多。
