宜兴通达竭诚为您服务。

zlib库剖析(2):编译及应用

2016-12-27 03:12:00     作者: Administrator     来源:互联网,版权归作者所有     浏览次数: 310     文字大小:【】【】【

1、编译zlib库

   在Linux下编译比较简单,在源码包中的Makefile.in中有说明。要编译和测试,在命令行下输入./configure; make test,通常会生成静态库(.a)和共享库(.so,类似windows下面的.dll)。如果只想编译成静态库,用./configure --static。为了安装到/usr/local/lib/libz.*和/usr/local/include/zlib.h,运行make install,为了安装到$HOME而不是/usr/local,运行make install prefix=$HOME。

   对Windows,使用win32/中的相应makefile或contrib/vstudio/中的相应Visual Studio工程文件来编译。对VMS,使用make_vms.com编译。直接用VS 2010打开contrib\vstudio\vc10\下的solution文件,运行"Build Solution,即可编译整个solution,包括zlibvc, miniunz, minizip, testzlib, testzlibdll, zlibstat。为了更好地理解编译过程,我们自己用VS 2010来创建编译工程,把x86版本的zlib编译成DLL动态库或LIB静态库。

   打开VS 2010,使用Visual C++视图界面。创建一个Empty Project,Name为zlibvc,Location指向zlib-1.2.7\contrib\目录,去掉勾选“Create directory for solution”。完成后在contrib\libvc\下生成一个zlibvc solution,并有一个空的zlibvc project。

   右项目zlibvc,选"Add--->Existing Item...",定位到zlib-1.2.7的根目录,选中所有源文件(.c和.h文件),添加到项目中来。把contrib\minizip\下的除miniunz.c和minizip.c的其他源文件也添加进来,这里包含读写zip文件的导出函数。

   添加一个Resource文件zlibvc.rc,在这个资源文件添加一个Version资源,设置DLL的一些描述信息:

 

1
2
3
4
5
6
7
FileDescription: zlib data compression and ZIP file I/O library
   FileVersion: 1.2.7
   InternalName: zlib
   LegalCopyright: (C) 1995-2012 Jean-loup Gailly & Mark Adler
   OriginalFilename: zlib.dll
   ProductName: ZLib.DLL
   ProductVersion: 1.0.0.1

   然后打开这个项目的属性,设置各个选项。

   (1)General选项

1
2
3
4
5
6
Output Directory: x86\ZlibDll$(Configuration)\
Intermediate Directory: x86\ZlibDll$(Configuration)\Tmp\
Target Name: zlib127
Target Extension: .dll
Configuration Type: Dynamic Library (.dll)。这里可以设置编译成动态库还是静态库。
Character Set: Empty

   (2)C/C++选项

   Additional Include Directories: 添加..\..和..\masmx86,其余从parent继承。这里contrib\masmx86\下包含了函数longest_match()和inflate_fast()的Windows汇编实现,match686.asm和inffas32.asm。

   Preprocessor Definitions: 添加WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_DLL;ASMV;ASMINF;其余从parent继承。这里ZLIB_DLL不是必须的,参看zconf.h。它会添加DLL优化标志,使DLL有一些性能优化。注意如果想编译成使用WINAPI/WINAPIV调用方式的DLL,则还需要添加ZLIB_WINAPI预处理宏(参看zconf.h)。我们使用默认的__stdcall调用方式。

   Buffer Security Check: No (/GS-)

   Program Database File Name: $(OutDir)

   (3)Linker选项

   Enable Incremental Linking: Yes (/INCREMENTAL)

   Additional Dependencies: 添加..\masmx86\match686.obj和..\masmx86\inffas32.obj,即两个汇编文件编译成的目标文件,其余从parent继承。

   (4)Resources选项

   Preprocessor Definitions: 添加_DEGUG,其余从parent继承。

   (5)Build Events选项

   Pre-Build Event:Command Line中添加命令cd ..\masmx86和bld_ml32.bat,表示在编译源代码之前要先运行..\masmx86\bld_ml32.bat,以编译match686.asm和inffas32.asm。

   右击项目zlibvc,运行"Build",即可在x86\ZlibDllDebug\下生成zlib127.dll,上面会显示DLL的版本1.0.1,及描述"zlib data compression and ZIP file I/O library"。右击zlib127.dll,查看属性--->版本,可以看到Version资源中的一些描述信息。如果没有模块定义文件,VS 2010生成DLL时,不会生成LIB静态库文件。我们需要手动添加def文件,通过"Add--->New Item...",新建一个模块定义文件zlibvc.def,内容参考contrib\vstudio\vc10\中的zlibvc.def,复制过来即可。def文件包含需要导出的所有函数列表。在项目Linker选项的Module Definition File中填写刚才定义的def文件".\zlibvc.def",然后重新编译即可生成LIB文件。注意如果在新建dll项目或空白项目时选择空白文件(即不让VS帮你生成),则一定要自已手动添加def文件,否则在生成dll时不会附带生成lib文件(除非我们直接把项目编译成lib文件)。

   使用的时候把lib和dll以及h文件zlib.h, zconf.h导出即可。

  2、应用zlib库

   我们通过contrib\minizip\下的miniunz.c来使用编译好的zlib127.dll。在solution zlibvc下新建空项目miniunz,生成的三个工程文件在contrib\zlibvc\miniunz\下,可以把它们拷贝到上一级目录contrib\zlibvc\下,在solution中打开它。通过Add--->Existing Item...,添加contrib\minizip\miniunz.c到项目中,在项目属性的"Framework and References"中添加对项目zlibvc的引用,FullPath中会显示zlib127.dll的完整路径。设置各个选项。

   (1)General选项

   Output Directory: x86\MiniUnzip$(Configuration)\

   Intermediate Directory: x86\MiniUnzip$(Configuration)\Tmp\

   (2)C/C++选项

   Additional Include Directories: 添加根目录..\..和目录..\minizip,其余从parent继承。根目录中有zlib.h和zconf.h,..\minizip中有miniunz的一系列源文件。

   Preprocessor Definitions: 添加WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_DLL;_DEBUG;_CONSOLE; 其余从parent继承。如果前面编译的是WINAPI格式的DLL,则使用DLL时这里也

要添加ZLIB_WINAPI。

   (3)Linker选项

   Additional Dependencies: 添加x86\ZlibDllDebug\zlib127.lib; 其余从parent继承。zlib172.lib在链接器生成miniunz.exe时需要用到。

   编译miniunz项目,即可生成miniunz.exe工具,这是一个简单的解压ZIP文件的命令行工具,还需要把zlib127.dll拷贝到miniunz.exe所在目录下,就可以使用了。这种使用zlib127.dll的方法没有对miniunz.c源码做任何改动,因为我们通过IDE添加了项目引用,在C/C++选项中添加了头文件的包含路径,在Linker中添加了导入库的引用。

  3、独立使用ZLIB DLL

   在开发DLL时,生成的.dll文件就是动态链接库,.lib是供程序开发用的导入库,.h文件包含了导出函数的声明。调用Dll中的导出函数有两种方法:

   (1)装载期间动态加载

   应用程序可像调用本地函数一样调用从其他Dll导出的函数(Windows API函数就是这样调用的)。装载期间链接必须使用DLL的导入库(.lib)文件,它为系统提供了加载这个Dll和定位Dll中的导出函数所需的信息。应用程序启动时由载入器(加载应用程序的组件)载入zlib127.dll文件。载入器如何知道要载入哪些Dll呢?这些信息记录在执行文件(PE文件)的idata节中。使用这种方法不用自己写代码显式加载DLL。

   新建一个zlibtest空项目,将zlib127.dll, zlib127.lib, zlib.h, zconf.h拷贝到zlibtest目录下。在项目中通过"Add--->Existing Item..."添加这两个头文件。新建一个testzlib.cpp源文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <stdlib.h>
#include "zlib.h"
#pragma comment(lib,"zlib127")
using namespace std;
#define MaxBufferSize 1024*10
int main(int argc, char *argv[])
{
    int i;
    FILE* srcFile;
    FILE* tmpFile;
    FILE* destFile;
    unsigned long lenSrc, lenTmp, lenDest;
                 
    unsigned char *bufferSrc=new unsigned char[MaxBufferSize];
    unsigned char *bufferTmp=new unsigned char[MaxBufferSize];
    unsigned char *bufferDest=new unsigned char[MaxBufferSize];
    srcFile=fopen("src.txt","r");
    lenSrc=fread(bufferSrc,sizeof(char),MaxBufferSize-1,srcFile);
    cout<<"#### Current readed data:"<<endl;
    for(i=0; i<lenSrc; ++i)  // print the readed data
    {
        cout<<bufferSrc[i];
    }
    cout<<endl<<endl;
    // compress the readed data
    compress(bufferTmp,&lenTmp,bufferSrc,lenSrc);
    tmpFile=fopen("temp.txt","w");
    // write the compressed data to temp file
    fwrite(bufferTmp,sizeof(char),lenTmp,tmpFile);
    cout<<"#### Current compressed data:"<<endl;
    for(i=0; i<lenTmp; ++i)  // print the compressed data
    {
        cout<<bufferTmp[i];
    }
    cout<<endl<<endl;
                 
    // uncompress  the data
    uncompress(bufferDest,&lenDest,bufferTmp,lenTmp);
    destFile=fopen("dest.txt","w");
    //write the uncompressed data
    fwrite(bufferDest,sizeof(char),lenDest,destFile);
    cout<<"#### Current uncompressed data:"<<endl;
    for(i=0; i<lenDest; ++i)  // print the compressed data
    {
        cout<<bufferDest[i];
    }
    cout<<endl<<endl;
    fclose(srcFile);
    fclose(tmpFile);
    fclose(destFile);
    delete [] bufferSrc;
    delete [] bufferTmp;
    delete [] bufferDest;
    return EXIT_SUCCESS;
}

   编译后生成zlibtest.exe。发布软件时必须将该软件使用的Dll与主程序一起发布。zlibtest.exe和zlib127.dll放在同一个目录下。载入器加载Dll文件时,默认情况是在应用程序的当前目录下查找,如果找不到就到系统盘"\windows\system32"文件夹下查找,还找不到就按错误处理。

   (2)运行期间动态加载(只需Dll文件即可)

   运行期间动态加载是在程序运行过程中显式得加载Dll库,从中导出需要的函数。为了能够在运行期间动态导出函数,一般需要在创建Dll时建立一个DEF文件来指定要导出的函数,我们在前面已经做了这一步。

   回到zlibtest工程,将程序修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
using namespace std;
#define MaxBufferSize 1024*10
typedef int (*dll_compress)(unsigned char*, unsigned long*, const unsigned char*, unsigned long);
typedef int (*dll_uncompress)(unsigned char*, unsigned long*, const unsigned char*, unsigned long);
           
int main(int argc, char *argv[])
{
    // load dll
    HMODULE hModule=::LoadLibrary(TEXT("zlib127.dll"));
    if(hModule==NULL)  // load failed
    {
        cout<<"Can't load zlib dll!"<<endl;
        return 1;
    }
    // get the exported function address
    dll_compress compress=(dll_compress)::GetProcAddress(hModule, "compress");
    if(compress==NULL)
    {
        cout<<"Can't get function compress from dll!"<<endl;
        return 1;
    }
    dll_uncompress uncompress=(dll_uncompress)::GetProcAddress(hModule, "uncompress");
    if(uncompress==NULL)
    {
        cout<<"Can't get function uncompress from dll!"<<endl;
        return 1;
    }
    int i;
    FILE* srcFile;
    FILE* tmpFile;
    FILE* destFile;
    unsigned long lenSrc, lenTmp, lenDest;
    unsigned char *bufferSrc=new unsigned char[MaxBufferSize];
    unsigned char *bufferTmp=new unsigned char[MaxBufferSize];
    unsigned char *bufferDest=new unsigned char[MaxBufferSize];
    srcFile=fopen("src.txt","r");
    lenSrc=fread(bufferSrc,sizeof(char),MaxBufferSize-1,srcFile);
    cout<<"#### Current readed data:"<<endl;
    for(i=0; i<lenSrc; ++i)  // print the readed data
    {
        cout<<bufferSrc[i];
    }
    cout<<endl<<endl;
    // compress the readed data
    compress(bufferTmp,&lenTmp,bufferSrc,lenSrc);
    tmpFile=fopen("temp.txt","w");
    // write the compressed data to temp file
    fwrite(bufferTmp,sizeof(char),lenTmp,tmpFile);
    cout<<"#### Current compressed data:"<<endl;
    for(i=0; i<lenTmp; ++i)  // print the compressed data
    {
        cout<<bufferTmp[i];
    }
    cout<<endl<<endl;
           
    // uncompress  the data
    uncompress(bufferDest,&lenDest,bufferTmp,lenTmp);
    destFile=fopen("dest.txt","w");
    //write the uncompressed data
    fwrite(bufferDest,sizeof(char),lenDest,destFile);
    cout<<"#### Current uncompressed data:"<<endl;
    for(i=0; i<lenDest; ++i)  // print the compressed data
    {
        cout<<bufferDest[i];
    }
    cout<<endl<<endl;
    fclose(srcFile);
    fclose(tmpFile);
    fclose(destFile);
    delete [] bufferSrc;
    delete [] bufferTmp;
    delete [] bufferDest;
    ::FreeLibrary(hModule); // free the dll library
    return EXIT_SUCCESS;
}

这里只需要一个zlib127.dll即可,无需头文件。包含头文件Windows.h,使用其LoadLibrary、GetProcAddress、FreeLibrary等Win32 API函数来加载Dll,获取导出函数,释放Dll。

   注意这里Dll二进制文件中的导出函数名没有改变,因为在zlib.h实现中加了extern "C"修饰导出函数名,表示在C++代码中仍然使用C中的函数命名方式。如果Dll实现时没有加extern "C",则表示使用C++命名方式,Dll中的导出函数名会改变,添加修饰名称,这也是C++函数重载机制得以实现的一个技术支持。打开VS 2010命令行窗口,使用VS 2010附带工具dumpbin可以查看Dll中的导出函数名。用dumpbin /EXPORTS zlib127.dll可查看这个dll所有导出的函数名(可以看到名称没有改变)。在调用GetProcAddress时应该使用查看到的函数名称,否则GetProcAddress会返回空值,这点特别要注意。

   动态链接库虽然一定程度上实现了黑盒复用,但仍存在着诸多不足,主要有下面几点。

   * Dll节省了编译期的时间,但相应延长了运行期的时间,因为在使用Dll的导出函数时,不但要加载Dll,而且程序将会在模块间跳转,降低了cache的命中率。

   * 若采用隐式调用,仍然需要.h、.lib、.dll文件(三件套),并不能有效支持模块的更新。

   * 显式调用(即只使用Dll)虽然很好地支持模块的更新,但却不能导出类和变量。

   * Dll不支持C++ Template。

   二进制级别的代码复用相比源码级别的复用已经有了很大的进步,但在二进制级别的代码复用中,Dll显得太古老。想真正完美实现跨平台、跨语言的黑盒复用,采用COM才是正确的选择。

   4、处理zip, gzip(.gz), .tar.gz(.tgz)文件

   在contrib和test目录中有相应实现例子。contrib\minizip\中的minizip.c/miniunz.c实现压缩、解压ZIP文件。test\minigzip.c实现压缩、解压gzip文件(用nmake运行win32\下的Makefile.msc可编译它)。不同于zip格式,gzip格式(.gz)只用于压缩单个文件。有多个文件时,通常先将它们合并成一个tar文件,再用gzip进行压缩。contrib\untgz\untgz.c实现一次性解压.tar.gz(.tgz)文件。

相关文章 评论

服务原则及地区范围

宜兴通达团队,在企业网络维护和企业信息化建设与咨询方面,有10多年经验。

我团队愿与客户一道,力求彻底解决客户问题!
我们不是在给企业提供“头痛医头、脚痛医脚”的暂时解决方案,而是在部署根本性安全与稳定服务!!
我们愿携手客户,建立企业IT规划;杜绝随意安装系统、软件等操作;力求共同维护有序、安全、稳定的网络办公环境!!!
IT服务,服务是根本,客户是上帝;我们提供快速响应、快速上门、快速排查,提供优质高效的服务!!!!

通达团队提供全国范围内的服务,服务形式包括远程协助、电话咨询、电子邮件咨询、传真咨询、问答平台的问题解决等。

宜兴地区提供上门服务:

  • 市区服务:宜城街道、城北街道(屺亭街道)、新街街道、新庄街道、环科园、渚桥开发区
  • 市郊服务:张渚镇、西渚镇、太华镇、徐舍镇、官林镇、杨巷镇、新建镇、和桥镇、高塍镇、万石镇、周铁镇、芳桥镇、丁蜀镇、湖父镇。
  • 联系电话:189-21-343434
  • 在线沟通: