【Linux技术】Windows与Linux系统下的库·初探
库的定义
库文件是一些预先编译好的函数的集合,那些函数都是按照可再使用的原则编写的。它们通常由一组互相关联的用来完成某项常见工作的函数构成,从本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。
无论在Windows 平台还是Linux平台下都存在大量的库,但由于Windows 和Linux 的本质不同,因此二者的库的二进制是不兼容的。
库的分类
- Windows 下的库有两种:静态库(.lib)和动态链接库(.dll)。
- Linux 下的库有两种:静态库(.a)和共享库(.so)。
- Linux 下的静态库的名字一般为libxxxx.a,其中xxxx 是该lib 的名称
- Linux 下的动态库的名字一般为libxxxx.so.major.minor,xxxx 是该lib 的名称,major 是主版本号,minor 是副版本号。
Windows与Linux下库的异同
Linux 的共享库(.so)就象Windows 的动态链接库(.dll),它里面包含有很多程序常用的函数。为了方便程序开发和减少程序的冗余,程序当中就不用包 含每个常用函数的拷贝,只是在需要时调用共享库中常函数即可。这种方式我们称之为动态链接(Dynamically Linked)。
而有时我们不希望叫程序去调用共享库的函数,而是把库函数代码直接链接进程序代码中,也就是说,在程序本身拥有一份共享库中函数的副本。 这种方式我们称之为静态链接(Statically Linked)。
所以,简单的讲静态库和共享库(动态库)的不同点在于代码被载入的时刻不同。
静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。
共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。
Windows下的动态链接库(dll)与Linux下的共享库(.so)的差别
.dll 文件事实上和.exe 文件一样,同属 PE格式的执行文件。对于隐式的引用外部符号,需要把外部符号所在的位置写在PE 头上。PE 加载器将从PE 头上找到依赖的符号表,并加载依赖的其它.dll 文件。
而在Linux 上并非如此!.so 文件大多为elf 执行文件格式。当它们需要的外部符号,可以不写明这些符号所在的位置。也就是说,通常.so 文件本身并不知道它依赖的那些符号在哪些.so 里面。这些符号是由调用dlopen 的进程运行时提供的。
我们在Windows 下做一个.dll 文件时还需要携带一个.lib 文件;而在Linux 下一般只需要有相应的头文件就够了。对于编写新的.so,找不到的 符号可以就让它在那里,直到最终执行文件来把所有需要的符号联合到一起。windows可以存在一个.dll 对另一个.dll 的隐式依赖;而Linux 下一般不需要让.so 和.so 有隐式依赖关系。
Windows与Linux系统的动态库差异分析
动态链接库技术实现和设计程序常用的技术,在 Windows 和Linux 系统中都有动态库的概念,采用动态库可以有效的减少程序大小,节省空间,提 高效率,增加程序的可扩展性,便于模块化管理。但不同操作系统的动态库由于格式 不同,在需要不同操作系统调用时需要进行动态库程序移植。
1. 引言
动态库(Dynamic Link Library abbr,DLL)技术是程序设计中经常采用的技术。其目的减少程序的大小,节省空间,提高效率,具有很高的灵活性。采用动态库技术对于升级软件版本更加 容易。与静态库(Static Link Library)不同,动态库里面的函数不是执行程序本身的一部分,而是根据执行需要按需载入,其执行代码可以同时在多个程序中共享。
在Windows 和Linux 操作系统中,都可采用这种方式进行软件设计,但他们的调用方式以及程序编制方式不尽相同。
2. Windows 动态库技术
动态链接库是实现Windows 应用程序共享资源、节省内存空间、提高使用效率的一个重要技术手段。常见的动态库包含外部函数和资源,也有一些 动态库只包含资源,如Windows 字体资源文件,称之为资源动态链接库。通常动态库以.dll,.drv、.fon 等作为后缀。相应的windows 静 态库通常以.lib 结尾,Windows 自己就将一些主要的系统功能以动态库模块的形式实现。
Windows 动态库在运行时被系统加载到进程的虚拟空间中,使用从调用进程的虚拟地址空间分配的内存,成为调用进程的一部分。DLL 也只能被 该进程的线程所访问。DLL 的句柄可以被调用进程使用;调用进程的句柄可以被DLL 使用。DLL 模块中包含各种导出函数,用于向外界提供服务。DLL 可以 有自己的数据段,但没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式;一个DLL 在内存中只有一个实例;DLL 实现了代码封装性;DLL的编制与具 体的编程语言及编译器无关,可以通过 DLL 来实现混合语言编程。DLL 函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。
根据调用方式的不同,对动态库的调用可分为静态调用方式和动态调用方式。
- 静态调用,也称为隐式调用,由编译系统完成对DLL 的加载和应用程序结束时DLL卸载的编码(Windows 系统负责对DLL 调用次数的 计数),调用方式简单,能够满足通常的要求。通常采用的调用方式是把产生动态连接库时产生的.LIB 文件加入到应用程序的工程中,想使用DLL 中的函数 时,只须在源文件中声明一下。 LIB 文件包含了每一个DLL导出函数的符号名和可选择的标识号以及 DLL 文件名,不含有实际的代码。Lib 文件包含的信息进入到生成的应用程序中,被调用的 DLL文件会在应用程序加载时同时加载在到内存中。
- 动态调用,即显式调用方式,是由编程者用API 函数加载和卸载DLL 来达到调用 DLL 的目的,比较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。
在Windows 系统中,与动态库调用有关的函数包括:
- LoadLibrary(或MFC 的AfxLoadLibrary),装载动态库。
- GetProcAddress,获取要引入的函数,将符号名或标识号转换为DLL 内部地址。
- FreeLibrary(或MFC 的AfxFreeLibrary),释放动态链接库。
在windows 中创建动态库也非常方便和简单。在Visual C 中,可以创建不用MFC 而直接用C 语言写的DLL 程序,也可以创建基于MFC 类库的DLL 程序。每一个DLL 必须有一个入口点,在VC中,DllMain 是一个缺省的入口函数。DllMain 负责初始化(Initialization)和结束(Termination)工作。动态库输出 函数也有两种约定,分别是基于调用约定和名字修饰约定。DLL 程序定义的函数分为内部函数和导出函数,动态库导出的函数供其它程序模块调用。
通常可以有下 面几种方法导出函数:
- 采用模块定义文件的EXPORT 部分指定要输入的函数或者变量。
- 使用MFC 提供的修饰符号_declspec(dllexport)。
- 以命令行方式,采用/EXPORT 命令行输出有关函数。
在windows 动态库中,有时需要编写模块定义文件(.DEF),它是用于描述DLL 属性的模块语句组成的文本文件。
3. Linux 共享对象技术
在Linux 操作系统中,采用了很多共享对象技术(Shared Object),虽然它和Windows里的动态库相对应,但它并不称为动态库。相应的共享对象文件以.so 作为后缀。
Linux 系统的/lib 以及标准图形界面的/usr/X11R6/lib等目录里面,就有许多以so 结尾的共享对象。同样,在Linux下,也有静态函数库这种调用方式,相应的后缀以.a 结束。Linux 采用该共享对象技术以方便程序间共享,节省程序占有空间,增加程序的可扩展性和灵活性。Linux 还可以通过LD-PRELOAD 变量让开发人员可以使用自己的程序库中的模块来替换系统模块。
同Windows 系统一样,在Linux 中创建和使用动态库是比较容易的事情,在编译函数库源程序时加上-shared 选项即可,这样所生成的执行程序就是动态链接库。通常这样的程序以 so为后缀,在Linux 动态库程序设计过程中,通常流程是编写用户的接口文件,通常是.h 文件,编写实际的函数文件,以.c 或.cpp 为后缀,再编写makefile 文件。对于较小的动态库程序可以不用如此,但这样设计使程序更加合理。
编译生成动态连接库后,进而可以在程序中进行调用。在Linux 中,可以采用多种调用方式,同Windows 的系统目录 (..\system32等)一样,可以将动态库文件拷贝到/lib 目录或者在/lib 目录里面建立符号连接,以便所有用户使用。
下面看看Linux 调用动态库经常使用的函数,但在使用动态库时,源程序必须包含dlfcn.h 头文件,该文件定义调用动态链接库的函数的原型。
- 打开动态链接库:dlopen,函数原型void *dlopen (const char *filename, int flag); dlopen 用于打开指定名字(filename)的动态链接库,并返回操作句柄。
- 取函数执行地址:dlsym,函数原型为: void *dlsym(void *handle, char *symbol); dlsym 根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。
- 关闭动态链接库:dlclose,函数原型为: int dlclose (void *handle); dlclose 用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。
- 动态库错误函数:dlerror,函数原型为: const char *dlerror(void); 当动态链接库操作函数执行失败时,dlerror 可以返回出错信息,返回值为NULL 时表示操作函数执行成功。
除了采用这种方式编写和调用动态库之外,Linux 操作系统也提供了一种更为方便的动态库调用方式,也方便了其它程序调用,这种方式与 Windows 系统的隐式链接类似。其动态库命名方式为“lib*.so.*”。在这个命名方式中,第一个*表示动态链接库的库名,第二个* 通常表示该动态库的版本号,也可以没有版本号。在这种调用方式中,需要维护动态链接库的配置文件/etc/ld.so.conf 来让动态链接库为系统所使用,通常将动态链接库所在目录名追加到动态链接库配置文件中。
如具有 X window 窗口系统发行版该文件中都具有/usr/X11R6/lib,它指向X window 窗口系统的动态链接库所在目录。为了使动态链接库能为系统所共享,还需运行动态链接库的管理命令./sbin/ldconfig。
在编译所引用的动态库时,可以在 gcc采用 -l 或-L 选项或直接引用所需的动态链接库方式进行编译。在 Linux 里面,可以采用ldd 命令来检查程序依赖共享库。
4. 两种系统动态库比较分析
Windows 和Linux 采用动态链接库技术目的是基本一致的,但由于操作系统的不同,他们在许多方面还是不尽相同。
- 动态库程序编写,在Windows 系统下的执行文件格式是PE 格式,动态库需要一个DllMain函数作为初始化的人口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。Linux 下的gcc 编译的执行文件默认是ELF 格式,不需要初始化入口,亦不需要到函数做特别声明,编写比较方便。
- 动态库编译,在windows 系统下面,有方便的调试编译环境,通常不用自己去编写makefile 文件,但在linux 下面,需要自己动手去编写makefile文件,因此,必须掌握一定的makefile 编写技巧,另外,通常Linux 编译规则相对严格。
- 动态库调用方面,Windows 和Linux 对其下编制的动态库都可以采用显式调用或隐式调用,但具体的调用方式也不尽相同。
- 动态库输出函数查看,在Windows 中,有许多工具和软件可以进行查看DLL 中所输出的函数,例如命令行方式的dumpbin 以及VC 工具中的DEPENDS 程序。在Linux 系统中通常采用nm 来查看输出函数,也可以使用ldd 查看程序隐式链接的共享对象文件。
- 对操作系统的依赖,这两种动态库运行依赖于各自的操作系统,不能跨平台使用。因此,对于实现相同功能的动态库,必须为两种不同的操作系统提供不同的动态库版本。