摘要:动态连接库技术实现和设计程序经常使用的技术,在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用于关闭指定句柄的动态连接库,只有当此动态连接库的使用计数为时,才会真正被系统卸载。
(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++提供的调试环境中完成初始的开发,毕竟VC++提供的图形化编辑和调试界面比vi和gcc方便许多。完成测试以后,再进行动态库的程序移植。
一般gcc默认的编译规则比VC++默认的编译规则严格,即便在VC++下面没有任何警告错误的程序在gcc调试中也会出现许多警告错误,能够在gcc中采用-w选项关闭警告错误。
下面给出程序移植须要遵循的规则以及经验。
(1) 尽可能不要改变原有动态库头文件的顺序。一般在C/C++语言中,头文件的顺序有至关的关系。另外虽然C/C++语言区分大小写,但在包含头文件 时,Linux必须与头文件的大小写相同,由于ext2文件系统对文件名是大小写敏感,不然不能正确编译,而在Windows下面,头文件大小写能够正确 编译。
(2)不一样系统独有的头文件。在Windows系统中,一般会包括windows.h头文件,若是调用底层的通讯函数,则会包含 winsock..h头文件。所以在移植到Linux系统时,要注释掉这些Windows系统独有的头文件以及一些windows系统的常量定义说明,增 加Linux都底层通讯的支持的头文件等。
(3)数据类型。VC++具备许多独有的数据类型,如 __int16,__int32,TRUE,SOCKET等,gcc编译器不支持它们。一般作法是须要将windows.h和basetypes.h中对 这些数据进行定义的语句复制到一个头文件中,再在Linux中包含这个头文件。例如将套接字的类型为SOCKET改成int。
(4)关键字。VC++中具备许多标准C中所没有采用的关键字,如BOOL,BYTE,DWORD,__asm等,一般在为了移植方便,尽可能不使用它们,若是实在没法避免能够采用#ifdef 和#endif为LINUX和WINDOWS编写两个版本。
(5)函数原型的修改。一般若是采用标准的C/C++语言编写的动态库,基本上不用再从新编写函数,但对于系统调用函数,因为两种系统的区别,须要改变函数的调用方式等,如在Linux编制的网络通讯动态库中,用close()函数代替windows操做系统下的closesocket()函数来关闭套接字。另外在Linux下没有文件句柄,要打开文件可用open和fopen函数,具体这两个函数的用法可参考文献[2]。
(6)makefile 的编写。在windows下面一般由VC++编译器来负责调试,但gcc须要本身动手编写makefile文件,也能够参照VC++生成的 makefile文件。对于动态库移植,编译动态库时须要加入-shared选项。对于采用数学函数,如幂级数的程序,在调用动态库是,须要加入-lm。
(7)其它一些须要注意的地方
①程序设计结构分析,对于移植它人编写的动态库程序,程序结构分析是必不可少的步骤,一般在动态库程序中,不会包含界面等操做,因此相对容易一些。
②在Linux中,对文件或目录的权限分为拥有者、群组、其它。因此在存取文件时,要注意对文件是读仍是写操做,若是是对文件进行写操做,要注意修改文件或目录的权限,不然没法对文件进行写。
③ 指针的使用,定义一个指针只给它分配四个字节的内存,若是要对指针所指向的变量赋值,必须用malloc函数为它分配内存或不把它定义为指针而定义为变量 便可,这点在linux下面比windows编译严格。一样结构不能在函数中传值,若是要在函数中进行结构传值,必须把函数中的结构定义为结构指针。
④路径标识符,在Linux下是“/”,在Windows下是“\”,注意windows和Linux的对动态库搜索路径的不一样。
⑤编程和调试技巧方面。对不一样的调试环境有不一样的调试技巧,在这里很少叙述。
5、结束语
本 文系统分析了windows和Linux动态库实现和使用方式,从程序编写、编译、调用以及对操做系统依赖等方面综合分析比较了这两种调用方式的不一样之 处,根据实际程序移植经验,给出了将VC++编制的Windows动态库移植到Linux下的方法以及须要注意的问题,同时并给出了程序示例片段,实际在 程序移植过程当中,因为系统的设计等方面,可能移植起来须要注意的方面远比上面复杂,本文经过总结概括进而为不一样操做系统程序移植提供了有意的经验和技巧。 html