深入分析Windows和Linux动态库应用异同
作者:刘世?杨林
摘要Q动态链接库技术实现和设计E序常用的技术,在Windows和Linuxpȝ中都有动态库的概念,采用动态库可以有效的减程序大,节省I间Q提高效率,增加E序的可扩展性,便于模块化管理?/P>
但不同操作系l的动态库׃格式 不同Q在需要不同操作系l调用时需要进行动态库E序UL。本文分析和比较了两U操作系l动态库技术,q给ZVisual C++~制的动态库UL到Linux上的Ҏ和经验?/P>
1、引a
动态库QDynamic Link Library abbrQDLLQ技术是E序设计中经帔R用的技术。其目的减少E序的大,节省I间Q提高效率,h很高的灵zL?/P>
采用动态库技术对于升UY件版本更加容易。与静态库QStatic Link LibraryQ不同,动态库里面的函C是执行程序本w的一部分Q而是Ҏ执行需要按需载入Q其执行代码可以同时在多个程序中׃n?/P>
在Windows和Linux操作pȝ中,都可采用q种方式q行软g设计Q但他们的调用方式以及程序编制方式不相同。本文首先分析了在这两种操作pȝ中通常采用的动态库调用Ҏ以及E序~制方式Q然后分析比较了q两U方式的不同之处Q最后根据实际移植程序经验,介绍了将VC++~制的Windows动态库UL到Linux下的Ҏ?/P>
2、动态库技?/P>
2.1 Windows动态库技?/P>
动态链接库是实现Windows应用E序׃n资源、节省内存空间、提高用效率的一个重要技术手Dc常见的动态库包含外部函数和资源,也有一些动态库只包含资源,如Windows字体资源文gQ称之ؓ资源动态链接库。通常动态库?dllQ?drv?fon{作为后~?/P>
相应的windows静态库通常?libl尾QWindows自己将一些主要的pȝ功能以动态库模块的Ş式实现?/P>
Windows动态库在运行时被系l加载到q程的虚拟空间中Q用从调用q程的虚拟地址I间分配的内存,成ؓ调用q程的一部分。DLL也只能被该进E的U程所讉K。DLL的句柄可以被调用q程使用Q调用进E的句柄可以被DLL使用?/P>
DLL模块中包含各U导出函敎ͼ用于向外界提供服务。DLL可以有自q数据D,但没有自q堆栈Q用与调用它的应用E序相同的堆栈模式;一个DLL在内存中只有一个实例;DLL实现了代码封装性;DLL的编制与具体的编E语a及编译器无关Q可以通过DLL来实现合语a~程。DLL函数中的代码所创徏的Q何对象(包括变量Q都归调用它的线E或q程所有?/P>
Ҏ调用方式的不同,对动态库的调用可分ؓ静态调用方式和动态调用方式?/P>
(1)静态调用,也称为隐式调用,q译系l完成对DLL的加载和应用E序l束时DLL卸蝲的编码(Windowspȝ负责对DLL调用ơ数的计敎ͼQ调用方式简单,能够满通常的要求。通常采用的调用方式是把生动态连接库时生的.LIB文g加入到应用程序的工程中,想用DLL中的函数Ӟ只须在源文g中声明一下?/P>
LIB文g包含了每一个DLL导出函数的符号名和可选择的标识号以及DLL文g名,不含有实际的代码。Lib文g包含的信息进入到生成的应用程序中Q被调用的DLL文g会在应用E序加蝲时同时加载在到内存中?/P>
(2)动态调用,x式调用方式,是由~程者用API函数加蝲和卸载DLL来达到调用DLL的目的,比较复杂Q但能更加有效地使用内存Q是~制大型应用E序时的重要方式。在Windowspȝ中,与动态库调用有关的函数包括:
①LoadLibraryQ或MFC 的AfxLoadLibraryQ,装蝲动态库?/P>
②GetProcAddressQ获取要引入的函敎ͼ符号名或标识号转换为DLL内部地址?/P>
③FreeLibraryQ或MFC的AfxFreeLibraryQ,释放动态链接库?/P>
在windows中创建动态库也非常方便和单。在Visual C++中,可以创徏不用MFC而直接用C语言写的DLLE序Q也可以创徏ZMFCcd的DLLE序。每一个DLL必须有一个入口点Q在VC++中,DllMain是一个缺省的入口函数。DllMain负责初始?Initialization)和结?Termination)工作?/P>
动态库输出函数也有两种U定Q分别是Z调用U定和名字修饰约定。DLLE序定义的函数分为内部函数和导出函数Q动态库导出的函C其它E序模块调用。通常可以有下面几U方法导出函敎ͼ
①采用模块定义文件的EXPORT部分指定要输入的函数或者变量?/P>
②用MFC提供的修饰符号_declspec(dllexport)?/P>
③以命o行方式,采用/EXPORT命o行输出有兛_数?/P>
在windows动态库中,有时需要编写模块定义文?.DEF)Q它是用于描qDLL属性的模块语句l成的文本文件?/P>
2.2 Linux׃n对象技?/P>
在Linux操作pȝ中,采用了很多共享对象技术(Shared ObjectQ,虽然它和Windows里的动态库相对应,但它q不UCؓ动态库。相应的׃n对象文g?so作ؓ后缀Qؓ了方便,在本文中Q对该概念不q行专门区分。Linuxpȝ?lib以及标准囑Ş界面?usr/X11R6/lib{目录里面,有许多以sol尾的共享对象?/P>
同样Q在Linux下,也有静态函数库q种调用方式Q相应的后缀?al束。Linux采用该共享对象技术以方便E序间共享,节省E序占有I间Q增加程序的可扩展性和灉|性。Linuxq可以通过LD-PRELOAD变量让开发h员可以用自qE序库中的模块来替换pȝ模块?/P>
同Windowspȝ一P在Linux中创建和使用动态库是比较容易的事情Q在~译函数库源E序时加?shared选项卛_Q这h生成的执行程序就是动态链接库。通常q样的程序以so为后~Q在Linux动态库E序设计q程中,通常程是编写用L接口文gQ通常?h文gQ编写实际的函数文gQ以.c?cpp为后~Q再~写makefile文g。对于较的动态库E序可以不用如此Q但q样设计使程序更加合理?/P>
~译生成动态连接库后,q而可以在E序中进行调用。在Linux中,可以采用多种调用方式Q同Windows的系l目?..\system32{?一P可以动态库文g拯?lib目录或者在/lib目录里面建立W号q接Q以便所有用户用?/P>
下面介绍Linux调用动态库l常使用的函敎ͼ但在使用动态库Ӟ源程序必d含dlfcn.h头文Ӟ该文件定义调用动态链接库的函数的原型?/P>
(1)_打开动态链接库QdlopenQ函数原型void *dlopen (const char *filename, int flag); dlopen用于打开指定名字(filename)的动态链接库Qƈq回操作句柄?/P>
(2)取函数执行地址QdlsymQ函数原型ؓ: void *dlsym(void *handle, char *symbol); dlsymҎ动态链接库操作句柄(handle)与符?symbol)Q返回符号对应的函数的执行代码地址?/P>
(3)关闭动态链接库QdlcloseQ函数原型ؓ: int dlclose (void *handle); dlclose用于关闭指定句柄的动态链接库Q只有当此动态链接库的用计Cؓ0?才会真正被系l卸载?/P>
(4)动态库错误函数QdlerrorQ函数原型ؓ: const char *dlerror(void); 当动态链接库操作函数执行p|Ӟdlerror可以q回出错信息Q返回gؓNULL时表C操作函数执行成功?/P>
在取到函数执行地址后,可以在动态库的用程序里面根据动态库提供的函数接口声明调用动态库里面的函数。在~写调用动态库的程序的makefile文gӞ需要加入编译选项-rdynamic?ldl?/P>
除了采用q种方式~写和调用动态库之外QLinux操作pȝ也提供了一U更为方便的动态库调用方式Q也方便了其它程序调用,q种方式与Windowspȝ的隐式链接类伹{其动态库命名方式为“lib*.so.*”。在q个命名方式中,W一?表示动态链接库的库名,W二?通常表示该动态库的版本号Q也可以没有版本受?/P>
在这U调用方式中Q需要维护动态链接库的配|文?etc/ld.so.conf来让动态链接库为系l所使用Q通常动态链接库所在目录名q加到动态链接库配置文g中。如hX windowH口pȝ发行版该文g中都h/usr/X11R6/libQ它指向X windowH口pȝ的动态链接库所在目录?/P>
Z使动态链接库能ؓpȝ所׃nQ还需q行动态链接库的管理命?/sbin/ldconfig。在~译所引用的动态库Ӟ可以在gcc采用 –l?L选项或直接引用所需的动态链接库方式q行~译。在Linux里面Q可以采用ldd命o来检查程序依赖共享库?/P>
3、两U系l动态库比较分析
Windows和Linux采用动态链接库技术目的是基本一致的Q但׃操作pȝ的不同,他们在许多方面还是不相同,下面从以下几个方面进行阐q?/P>
(1)动态库E序~写Q在Windowspȝ下的执行文g格式是PE格式Q动态库需要一个DllMain函数作ؓ初始化的人口Q通常在导出函数的声明旉要有_declspec(dllexport)关键字。Linux下的gcc~译的执行文仉认是ELF格式Q不需要初始化入口Q亦不需要到函数做特别声明,~写比较方便?/P>
(2)动态库~译Q在windowspȝ下面Q有方便的调试编译环境,通常不用自己ȝ写makefile文gQ但在linux下面Q需要自己动手去~写makefile文gQ因此,必须掌握一定的makefile~写技巧,另外Q通常Linux~译规则相对严格?/P>
(3)动态库调用斚wQWindows和Linux对其下编制的动态库都可以采用显式调用或隐式调用Q但具体的调用方式也不尽相同?/P>
(4)动态库输出函数查看Q在Windows中,有许多工具和软g可以q行查看DLL中所输出的函敎ͼ例如命o行方式的dumpbin以及VC++工具中的DEPENDSE序。在Linuxpȝ中通常采用nm来查看输出函敎ͼ也可以用ldd查看E序隐式链接的共享对象文件?/P>
(5)Ҏ作系l的依赖Q这两种动态库q行依赖于各自的操作pȝQ不能跨q_使用。因此,对于实现相同功能的动态库Q必Mؓ两种不同的操作系l提供不同的动态库版本?/P>
4、动态库ULҎ
如果要编制在两个pȝ中都能用的动态链接库Q通常会先选择在Windows的VC++提供的调试环境中完成初始的开发,毕竟VC++提供的图形化~辑和调试界面比vi和gcc方便许多。完成测试之后,再进行动态库的程序移植?/P>
通常gcc默认的编译规则比VC++默认的编译规则严|即在VC++下面没有M警告错误的程序在gcc调试中也会出现许多警告错误,可以在gcc中采?w选项关闭警告错误?/P>
下面l出E序UL需要遵循的规则以及l验?/P>
(1)量不要改变原有动态库头文件的序。通常在C/C++语言中,头文件的序有相当的关系。另外虽然C/C++语言区分大小写,但在包含头文件时QLinux必须与头文g的大写相同Q因为ext2文gpȝҎ件名是大写敏感Q否则不能正编译,而在Windows下面Q头文g大小写可以正编译?/P>
(2)不同pȝ独有的头文g。在Windowspȝ中,通常会包括windows.h头文Ӟ如果调用底层的通信函数Q则会包含winsock..h头文件。因此在UL到LinuxpȝӞ要注释掉q些Windowspȝ独有的头文g以及一些windowspȝ的常量定义说明,增加Linux都底层通信的支持的头文件等?/P>
(3)数据cd。VC++h许多独有的数据类型,如__int16Q__int32QTRUEQSOCKET{,gcc~译器不支持它们。通常做法是需要将windows.h和basetypes.h中对q些数据q行定义的语句复制到一个头文g中,再在Linux中包含这个头文g。例如将套接字的cd为SOCKET改ؓint?/P>
(4)关键字。VC++中具有许多标准C中所没有采用的关键字Q如BOOLQBYTEQDWORDQ__asm{,通常在ؓ了移植方便,量不用它们,如果实在无法避免可以采用#ifdef ?endif为LINUX和WINDOWS~写两个版本?/P>
(5)函数原型的修攏V通常如果采用标准的C/C++语言~写的动态库Q基本上不用再重新编写函敎ͼ但对于系l调用函敎ͼ׃两种pȝ的区别,需要改变函数的调用方式{,如在Linux~制的网l通信动态库中,用close()函数代替windows操作pȝ下的closesocket()函数来关闭套接字。另外在Linux下没有文件句柄,要打开文g可用open和fopen函数Q具体这两个函数的用法可参考文献[2]?/P>
(6)makefile的编写。在windows下面通常由VC++~译器来负责调试Q但gcc需要自己动手编写makefile文gQ也可以参照VC++生成的makefile文g。对于动态库ULQ编译动态库旉要加?shared选项。对于采用数学函敎ͼ如幂U数的程序,在调用动态库是,需要加?lm?/P>
(7)其它一些需要注意的地方
①程序设计结构分析,对于UL它h~写的动态库E序Q程序结构分析是必不可少的步骤,通常在动态库E序中,不会包含界面{操作,所以相对容易一些?/P>
②在Linux中,Ҏ件或目录的权限分为拥有者、群l、其它。所以在存取文gӞ要注意对文g是读q是写操作,如果是对文gq行写操作,要注意修Ҏ件或目录的权限,否则无法Ҏ件进行写?/P>
③指针的使用Q定义一个指针只l它分配四个字节的内存,如果要对指针所指向的变量赋|必须用malloc函数为它分配内存或不把它定义为指针而定义ؓ变量卛_Q这点在linux下面比windows~译严格。同L构不能在函数中传|如果要在函数中进行结构传|必须把函C的结构定义ؓl构指针?/P>
④\径标识符Q在Linux下是?”,在Windows下是“\”,注意windows和Linux的对动态库搜烦路径的不同?/P>
⑤编E和调试技巧方面。对不同的调试环境有不同的调试技巧,在这里不多叙q?/P>
5、结束语
本文pȝ分析了windows和Linux动态库实现和用方式,从程序编写、编译、调用以及对操作pȝ依赖{方面综合分析比较了q两U调用方式的不同之处Q根据实际程序移植经验,l出了将VC++~制的Windows动态库UL到Linux下的Ҏ以及需要注意的问题Q同时ƈl出了程序示例片断,实际在程序移植过E中Q由于系l的设计{方面,可能ULh需要注意的斚wq比上面复杂Q本文通过ȝ归纳q而ؓ不同操作pȝE序UL提供了有意的l验和技巧?BR>