From http://os.rdxx.com/Linux/LinuxRudiment/2008/5/17017197011.shtml
from http://www.blogjava.net/davidgw/archive/2009/01/21/252230.html
一,Linux平台下静态库与动态库的使用及编译
库有动态与静态两种,动态通常用.so为后缀,静态用.a为后缀。例如:libhello.so libhello.a
为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,例如: libhello.so.1.0,由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。
ln -s libhello.so.1.0 libhello.so.1
ln -s libhello.so.1 libhello.so
使用库
当 要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。然 而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记‘指明当程序执行时,首先必须载入这个库。由于动态库节省空间,linux下进行连接的 缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。
现在假设有一个叫hello的程序开发包,它提供一个静态库libhello.a 一个动态库libhello.so,一个头文件hello.h,头文件中提供sayhello()这个函数
/* hello.h */
void sayhello();
另外还有一些说明文档。这一个典型的程序开发包结构
1.与动态库连接
linux默认的就是与动态库连接,下面这段程序testlib.c使用hello库中的sayhello()函数
/*testlib.c*/
#include
#include
int main()
{
sayhello();
return 0;
}
使用如下命令进行编译(注意一下步骤是编译和链接分开进行的,也可以使用命令一步进行)
$gcc -c testlib.c -o testlib.o
用如下命令连接:
$gcc testlib.o -lhello -o testlib
在连接时要注意,假设libhello.o 和libhello.a都在缺省的库搜索路径下/usr/lib下,如果在其它位置要加上-L参数
与与静态库连接麻烦一些,主要是参数问题。还是上面的例子:
$gcc testlib.o -o testlib -WI,-Bstatic -lhello
注:这个特别的"-WI,-Bstatic"参数,实际上是传给了连接器ld.
指示它与静态库连接,如果系统中只有静态库当然就不需要这个参数了。
如果要和多个库相连接,而每个库的连接方式不一样,比如上面的程序既要和libhello进行静态连接,又要和libbye进行动态连接,其命令应为:
$gcc testlib.o -o testlib -WI,-Bstatic -lhello -WI,-Bdynamic -lbye
3.动态库的路径问题
为了让执行程序顺利找到动态库,有三种方法:
(1)把库拷贝到/usr/lib和/lib目录下。
(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。例如动态库libhello.so在/home/ting/lib目录下,以bash为例,使用命令:
$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ting/lib
(3) 修改/etc/ld.so.conf文件,把库所在的路径加到文件末尾,并执行ldconfig刷新。这样,加入的目录下的所有库文件都可见、
4.查看库中的符号
有 时候可能需要查看一个库中到底有哪些函数,nm命令可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。nm列出的符号有很多,常见的有 三种,一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;一种是库中定义的函数,用T表示,这是最常见的;另外一种是所谓的“弱 态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。例如,假设开发者希望知道上央提到的hello库中是否定义了 printf():
$nm libhello.so |grep printf
U printf
U表示符号printf被引用,但是并没有在函数内定义,由此可以推断,要正常使用hello库,必须有其它库支持,再使用ldd命令查看hello依赖于哪些库:
$ldd hello
libc.so.6=>/lib/libc.so.6(0x400la000)
/lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)
从上面的结果可以继续查看printf最终在哪里被定义,有兴趣可以go on
生成库
第一步要把源代码编绎成目标代码。以下面的代码为例,生成上面用到的hello库:
/* hello.c */
#include
void sayhello()
{
printf("hello,world
");
}
用gcc编绎该文件,在编绎时可以使用任何全法的编绎参数,例如-g加入调试代码等:
gcc -c hello.c -o hello.o
1.连接成静态库
连接成静态库使用ar命令,其实ar是archive的意思
$ar cqs libhello.a hello.o
2.连接成动态库
生成动态库用gcc来完成,由于可能存在多个版本,因此通常指定版本号:
$gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 hello.o
另外再建立两个符号连接:
$ln -s libhello.so.1.0 libhello.so.1
$ln -s libhello.so.1 libhello.so
这样一个libhello的动态连接库就生成了。最重要的是传gcc -shared 参数使其生成是动态库而不是普通执行程序。
-Wl 表示后面的参数也就是-soname,libhello.so.1直接传给连接器ld进行处理。实际上,每一个库都有一个soname,当连接器发现它正 在查找的程序库中有这样一个名称,连接器便会将soname嵌入连结中的二进制文件内,而不是它正在运行的实际文件名,在程序执行期间,程序会查找拥有 soname名字的文件,而不是库的文件名,换句话说,soname是库的区分标志。
这样做的目的主要是允许系统中多个版本的库文件共存,习惯上在命名库文件的时候通常与soname相同
libxxxx.so.major.minor
其中,xxxx是库的名字,major是主版本号,minor 是次版本号
=====================
二 分析Windows平台与Linux平台下动态库的异同点
摘要:动态链接库技术实现和设计程序常用的技术,在Windows和Linux系统中都有动态库的概念,采用动态库可以有效的减少程序大小,节省空间,提高效率,增加程序的可扩展性,便于模块化管理。但不同操作系统的动态库由于格式 不同,在需要不同操作系统调用时需要进行动态库程序移植。本文分析和比较了两种操作系统动态库技术,并给出了将Visual C++编制的动态库移植到Linux上的方法和经验。
1、引言
动态库(Dynamic Link Library abbr,DLL)技术是程序设计中经常采用的技术。其目的减少程序的大小,节省空间,提高效率,具有很高的灵活性。采用动态库技术对于升级软件版本更加容易。与静态库(Static Link Library)不同,动态库里面的函数不是执行程序本身的一部分,而是根据执行需要按需载入,其执行代码可以同时在多个程序中共享。
在Windows和Linux操作系统中,都可采用这种方式进行软件设计,但他们的调用方式以及程序编制方式不尽相同。本文首先分析了在这两种操作系统中通常采用的动态库调用方法以及程序编制方式,然后分析比较了这两种方式的不同之处,最后根据实际移植程序经验,介绍了将VC++编制的Windows动态库移植到Linux下的方法。
2、动态库技术
2.1 Windows动态库技术
动态链接库是实现Windows应用程序共享资源、节省内存空间、提高使用效率的一个重要技术手段。常见的动态库包含外部函数和资源,也有一些动态库只包含资源,如Windows字体资源文件,称之为资源动态链接库。通常动态库以.dll,.drv、.fon等作为后缀。相应的windows静态库通常以.lib结尾,Windows自己就将一些主要的系统功能以动态库模块的形式实现。
Windows动态库在运行时被系统加载到进程的虚拟空间中,使用从调用进程的虚拟地址空间分配的内存,成为调用进程的一部分。DLL也只能被该进程的线程所访问。DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。DLL模块中包含各种导出函数,用于向外界提供服务。DLL可以有自己的数据段,但没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式;一个DLL在内存中只有一个实例;DLL实现了代码封装性;DLL的编制与具体的编程语言及编译器无关,可以通过DLL来实现混合语言编程。DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。
根据调用方式的不同,对动态库的调用可分为静态调用方式和动态调用方式。
(1)静态调用,也称为隐式调用,由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码(Windows系统负责对DLL调用次数的计数),调用方式简单,能够满足通常的要求。通常采用的调用方式是把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须在源文件中声明一下。 LIB文件包含了每一个DLL导出函数的符号名和可选择的标识号以及DLL文件名,不含有实际的代码。Lib文件包含的信息进入到生成的应用程序中,被调用的DLL文件会在应用程序加载时同时加载在到内存中。
(2)动态调用,即显式调用方式,是由编程者用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属性的模块语句组成的文本文件。
2.2 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头文件,该文件定义调用动态链接库的函数的原型。
(1)_打开动态链接库:dlopen,函数原型void *dlopen (const char *filename, int flag);
dlopen用于打开指定名字(filename)的动态链接库,并返回操作句柄。
(2)取函数执行地址:dlsym,函数原型为: void *dlsym(void *handle, char *symbol);
dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。
(3)关闭动态链接库:dlclose,函数原型为: int dlclose (void *handle);
dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。
(4)动态库错误函数:dlerror,函数原型为: const char *dlerror(void); 当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。
在取到函数执行地址后,就可以在动态库的使用程序里面根据动态库提供的函数接口声明调用动态库里面的函数。在编写调用动态库的程序的makefile文件时,需要加入编译选项-rdynamic和-ldl。
除了采用这种方式编写和调用动态库之外,Linux操作系统也提供了一种更为方便的动态库调用方式,也方便了其它程序调用,这种方式与Windows系统的隐式链接类似。其动态库命名方式为“lib*.so.*”。在这个命名方式中,第一个*表示动态链接库的库名,第二个*通常表示该动态库的版本号,也可以没有版本号。在这种调用方式中,需要维护动态链接库的配置文件/etc/ld.so.conf来让动态链接库为系统所使用,通常将动态链接库所在目录名追加到动态链接库配置文件中。如具有X window窗口系统发行版该文件中都具有/usr/X11R6/lib,它指向X window窗口系统的动态链接库所在目录。为了使动态链接库能为系统所共享,还需运行动态链接库的管理命令./sbin/ldconfig。在编译所引用的动态库时,可以在gcc采用 ?l或-L选项或直接引用所需的动态链接库方式进行编译。在Linux里面,可以采用ldd命令来检查程序依赖共享库。
3、两种系统动态库比较分析
Windows和Linux采用动态链接库技术目的是基本一致的,但由于操作系统的不同,他们在许多方面还是不尽相同,下面从以下几个方面进行阐述。
(1)动态库程序编写,在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数作为初始化的人口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。Linux下的gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要到函数做特别声明,编写比较方便。
(2)动态库编译,在windows系统下面,有方便的调试编译环境,通常不用自己去编写makefile文件,但在linux下面,需要自己动手去编写makefile文件,因此,必须掌握一定的makefile编写技巧,另外,通常Linux编译规则相对严格。
(3)动态库调用方面,Windows和Linux对其下编制的动态库都可以采用显式调用或隐式调用,但具体的调用方式也不尽相同。
(4)动态库输出函数查看,在Windows中,有许多工具和软件可以进行查看DLL中所输出的函数,例如命令行方式的dumpbin以及VC++工具中的DEPENDS程序。在Linux系统中通常采用nm来查看输出函数,也可以使用ldd查看程序隐式链接的共享对象文件。
(5)对操作系统的依赖,这两种动态库运行依赖于各自的操作系统,不能跨平台使用。因此,对于实现相同功能的动态库,必须为两种不同的操作系统提供不同的动态库版本。
4、动态库移植方法
如果要编制在两个系统中都能使用的动态链接库,通常会先选择在Windows的VC++提供的调试环境中完成初始的开发,毕
————————————————————————————————————————————————————————
-------------------------------------------------------------------------------------------------------------------
三、Windows平台下静态库与动态库的使用
可能你还在用Visual C++平台或者VS2005编译源文件, 最后生成一个.exe或者DLL或者是lib文件,这一切都发生得那么神不知鬼不觉,测试一下,你是否了解下面的概念:
1. 一个dll或者exe文件的生成经过了哪些环节?
2. dll的调用有哪些方式?在这些不同的方式中,随DLL发布的.h文件, .lib文件,.dll文件将参与新的编译连接过程,那么他们分别参与到哪些过程.
3. #include<stdio.h> 是我们常用库,这个库,是静态库,动态库,还是其它, 位置如何?
能非常肯定2和3答案的,不是本文的读者,可以离开。^-^
第一: 编译的全过程, 以windows平台为例.
先看看整个编译的过程, 为了防止概念过多,去掉预处理,汇编等繁杂的概念, 因为这些我们触碰不到, 我们关注的核心就两个,编译(compile), 连接(link):
compile -> link
通过编译,能生成的东东有: .obj文件, lib文件. 注意.obj文件和lib都是源代码生成的符号,包含信息一致,在头脑里不要区分它们,就简单的认为它们都是一个东西.
编译的时候, 大家都是独立编译的, 所以只需要找到函数或者变量的声明 就可以。因此参与这个过程的, 只需要.h文件(没有.h的, 可以通过extern 声明, 也是没有任何问题的)
通过link, 把编译成成的.obj文件, lib文件的符号连接起来, 形成二进制文件(DLL 或者exe)
连接的时候, 这个过程务必要找到编译后的符号文件, 比如函数和变量的定义 。
对于编译生成的DLL, 这个会在运行的时候用到. DLL 包含的是二进制的地址信息, 所以, 如果我们需要用到DLL, 务必知晓其地址信息。 这个地址信息, 可以直接通过windows平台查找dll的方法(比如, 下文提到的DLL三种使用模式中的独立使用方式). 另外一种常见的方式是: 通过lib定位DLL的信息, 对应于下面的DLL + lib或者DLL + .h + lib的方式. 当然lib要参与连接生成exe或者dll, 而.h如果存在的话, 必定是参与compile. 这种方式, 正是上文提到的: 我们采用的库的方式.
第二:windows平台下的动态链接库和静态链接库
但对一个项目而言,可以切分,比如将项目分为A模块,其它模块。
如果我们先将A模块编译成二进制的DLL,然后再和其他模块合并,这个时候,就称为动态链接;
如果我们现将A模块编译成lib文件, 然后在和其他模块合并,这个时候,成为静态链接;
接下来,我们关注什么是静态链接,什么是静态链接。
对于静态连接,已经没有任何可讲的地方了, 还记得吗,lib文件相当于obj文件, 我们如何用obj文件,就如何用lib文件,也许你真的明白obj文件的使用,但这里还是要让你重温一下:
如果有三个C源文件:a.c, b.c , c.c,及其配套的.h文件:a.h, b.h,c.h, 通过编译,将产生a.obj, b.obj, c.obj三个obj文件.
这里,如果a,b,c之间不存在相互引用, 根本不需要头文件参与进来编译的, 但是,如果,a用了b的变量或者函数,在编译a的时候,必须要一个头文件来声明所引用的函数(可以不必是b.h文件,甚至是x.h文件,但里面必须有用到变量或者函数的声明)。
当这三个文件编译之后,再进行连接,则肯定可以成功。
静态链接的道理跟obj文件的编译是一个道理,假如把b看做是静态库lib,b.h则是静态库所对应的lib文件. 当a.c需要用到b.c
中的函数来编译时, 它只需要b.h文件,生成a.obj, 最后a.obj和b.lib文件连接生成我们需要的二进制文件。
动态链接库,即有dll参与的库文件使用,也就是dll的使用方式,可以有以下几种:
1. dll独立使用
2. dll + lib
3. dll + lib + .h
在DLL独立使用的情况下:windows下会有一个loadLibray的机制,将生成的DLL二进制地址信息加入,直接调用,所以DLL既不需要编译,也不需要连接, 扔给exe文件就可以调用,有人称之为动态加载 。
在含有lib文件的情况下, 库函数的使用,是要参与连接的, 这就是传说中的静态加载 . 其原理如下:
此时的lib文件,跟上面静态链接不同,不再是所有的源文件的函数和变量内容,而是仅仅是DLL中函数或者变量的符号,通过lib的指引,其它的函数调用DLL库中的函数,轻而易举。
至于.h文件的作用,当然是参与编译了;如果源文件中用到dll的函数, 不声明为extern或者通过dll发布的头文件生命的话,是无法编译通过的。
一般而言,发布了lib文件, 最好发布一下头文件,不然怎么知道lib中带了什么函数呢?当然如果你是lib的制作者,你了解lib里面的蛛丝马迹,当然,你用个extern搞定这些lib中的函数和变量,也未尝不可。
OK,这里一句话可以做结Windows下的库调用:
.h参与compile, lib参与link, dll 参与excute
第三: Linux平台下的动态链接库和静态链接库
在静态链接技术上,两者五区别,中间的.o文件相当与.a静态文件, 跟Windows平台类似,动态连接技术上,两者存在区别:动态链接文件叫做share object, .so文件,但理论一致,只是,这个.so文件,包含的信息不同,相当于windows下dll + lib的信息, 也就是说, so文件,要参与连接, 也参与执行,但是, 可执行程序并不依赖于参与连接的so文件,可以是其它任何时间任何地点,只要接口相同的so文件。这点跟windows下的dll完全一样。
在gcc平台可以测试上面的原理。编译器所作的工作,各个平台也是有一些区别的。如果用gcc编译,会对其中的奥秘有更多的理解,这样会让你更多的思考利用vc和windows平台时,你没有考虑过的问题。
最后开篇的第三个问题没有解决:
stdio的引用方式是哪种: 应该是动态链接库, 但是加载的方式是静态的。
stdio库包含了dll(system32目录下), stdio.h, stdio.lib.
标准库大多是采用这样的方式. 动态的加载方式, 仅限于自己自导自演的游戏。开源库,标准库,都是动态链接库的静态加载。