控制台下的DLL动态连接库的开发
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
控制台下的DLL动态连接库的
开发
摘要:
通常在开发大型的应用软件系统时,为了提高代码的重用性,降低代码间的耦合度,一般会采用模块化的编程方式,将应用程序分成很多模块,这些模块分别完成相对独立的功能,它们彼此协作构成整个软件系统。
在编程过程中可能有一些模块的功能较为通用,在构建其它软件系统时仍然可以使用。
于是Windows系统平台提供了一种有效的编程和运行环境,可以将独立的程序模块创建为较小的动态连接库-DLL(Dynamic Link Library/res)文件,并可对它们单独编译和测试。
本文简要说明了动态连接库的结构与开发过程,给出了应用程序调用DLL库的几种方式和相应的语言程序,讨论了函数参数传递的两种形式。
关键词:动态连接库静态连接库 MFC类库标准接口扩展接口
第一章动态连接库的概念与创建1.1.动态连接库的基本概念
动态连接库(Dynamic Link Library,DLL)是Windows编程的重要特点之一,它使得Windows应用程序可以共享DLL资源和代码,即在内存中只保留一份DLL程序库,由多个应用程序调用同一DLL副本,这在Windows这一多任务操作环境中可以大大的提高程序的执行效率,节省宝贵的资源,实际上Windows本身也包括几个重要DLL库,如GDI.EXE,USER.EXE,KERNEL.EXE均为动态连接库。
Windows应用程序是一个可执行文件(EXE),它通常创建一个或几个窗口,并使用消息循环接收用户输入。
动态连接库是一种包含函数和数据的模块,通常并不能直接执行,它们是一些独立的文件(通常是DLL文件),其中包含能被应用程序或其他DLL调用完成一定功能的函数。
只有在其它模块调用DLL中的函数时才起作用。
在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。
这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。
在程序开发中,将各种目标模块(OBJ),运行库(LIB)文件,以及已编译的资源(RES)文件连接在一起,创建可执行文件(EXE),这种连接称为“静态连接”。
而“动态连接”发生在运行时刻,是在运行时将已经编译调试好的DLL文件装入,它映射到调用程序进程的虚拟地址空间中。
DLL并不是一个独立的可执行程序,类似于传统编程的库程序,用户可以将他入或自己编制的DLL连接到应用程序中,但传统编程的库连接是静态连接(Static Link Library),将库程序中的代码和数据复制到自己的程序中,这在DOS环境下无疑提高了编程效率、但在Windows多任务环境下有可能同时运行多个应用程序,如果其中有两个以上的应用程序使用了同一静态库中的函数,那么内存中就会同时出现两份相同的
数据和代码,这显然浪费了内存资源,降低了效率,开发DLL程序就是为了避免出现这种情况。
相比静态连接库动态连接库的优点:
当多个应用程序同时调用同一个DLL时,所有程序将共享该DLL 在物理内存的同一份副本,这样节省了内存空间,减少了数据
交换的系统开销;
DLL可以独立编译、调试,当对DLL进行更改和升级时,只要DLL中出口函数的名称、参数、调用方式和返回值不发生改变,
调用该DLL的应用程序不需要重新编译和调试;
可以跨平台调用,DLL的编制与具体的编程语言及编译器无关,只要在开发DLL时遵循通用的标准,用一种编程语言开发的应
用程序可以调用其它编程语言开发的DLL,这增加了DLL的通用
性。
DLL通常采用.DLL为扩展名,但也有用。
DRV、.FON、.EXE等为扩展名的.例如各种驱动文件KEYBOARD.DRV,SOUND.DRV,视频和打印驱动等都是DLL,各种字体资源文件(.FON)也是DLL,只不过其中存储的既不是代码也不是数据,而是可供其它Windows程序使用的字模,虽然DLL可以有任意扩展名,但只有具有标准动态连接库扩展名.DLL的动态连接库会被Windows自动装入,其它扩展名的DLL必须显示用LoadLibrary函数调入。
1.2.创建动态连接库
1.2.1动态连接库的创建方式
动态连接库的创建方式主要可以分为C语言直接创建、MFC类库创建两大类。
直接用C语言写的DLL,其输出的函数一般用的是标准C接口来实现的,并且能够被非MFC和MFC编写的应用程序所调用,其通用性比较强,方便调用和整合。
使用MFC类库编写的DLL主要可以分为两
种:规则的动态连接库和扩展动态连接库。
规则动态链接库(RegularDLL)明显的特点是在源文件里有一个继承CWinApp的类,DllMain函数被MFC所提供,不需显式的写
出来。
其又可细分成静态连接到MFC和动态连接到MFC两种。
规则动态链接库(RegularDLL)能够被所有支持DLL技术的语言
所编写的应用程序调用。
“MGS图形软件开发包”提供的动态链
接库mgsdl1.dll、mgsSmap.dll和MgsEtSymdu就是规则动态
链接库(RegularDLL)。
扩展动态链接库(ExtensionDLL)用来实现从MFC所继承下来的类的重新利用,也就是说,用这种类型的动态连接库,可以用
来输出一个从MFC所继承下来的类。
它输出的函数仅可以被使
用MFC且动态链接到MFC的应用程序使用。
可以从MFC继承你
所想要的、更适于你自己用的类,并把它提供给应用程序。
也
可随意的给应用程序提供MFC或MFC继承类的对象指针。
扩展
动态链接库(ExtensionDLL)使用MFC的动态连接版本创建,它
只能被用MFC类库所编写的应用程序所调用。
扩展动态链接库
(ExtensionDLL)和规则动态链接库(RegularDLL)不一样,它没
有一个从CWinApp继承而来的类的对象,所以,你必须为自己
的DllMain函数添加初始化代码和结束代码。
DLL的建立同标准的Windows程序相类似每个DLL都有一个入口函数(LibMain),程序体函数(MainRoutine)和出口函数(WEP),其中只有入口函数是必需的.DLL可以具有自己的实例句柄,资源,数据段和局部堆,但是没有自己的堆栈只能使用调用者的堆栈.在中小模式下程序数据段与堆栈段通常指向同一段地址,因此DLL在此情况下有可能发生DS=SS 的错误,必须在编译时设置相应的选项来检查这些错误。
1.2.2动态连接库的创建规则
DLL函数不能使用Windows的消息循环,它的模块定义文件也必须符合以下一些限制:
NAME段声明为LIBRARY,其声明的名称为生成的DLL库名;
DATA段声明中的MULTIPLE项改为SINGLE,因为DLL只允许单个副本;
没有STACKSIZE声明,因为DLL没有堆栈。
可声明HEAPSIZE段;
如果DLL建立出口函数WEP,则WEP必须包含在EXPORTS声明中;
DLL可以有自己的内部函数,但可供其它程序调用的输出函数必须包含在EXPORTS声明中。
一个DLL中可以有多个输出函数,
输出函数必须用FARPASCAL显式声明其函数原型。
DLL的C语言源文件必须包含入口函数LibMain(),它有4个入口参数,LibMain()是对DLL进行初始化工作;出口函数WEP()在DLL被卸载时作一些必要的清理工作。
因此建立一个动态连接库至少包含源文件和模块定义文件两个源文件,还可以包括.H头文件,.RC资源文件等.下面是用Borland C++ IDE建立DLL的几个标准框架文件:
1)工程文件名:dlldemo.Prj;工程文件项:dlldemo.def,dlldemo.C
2)C语言源文件DLLDEMO.C
#include<windows.h>
inkFARPASCALexportdemofl(intfarpath,intfar*databuf);
intFARPASCALdemof2(intfarpath。
intfardata[I28]);
intPASCALFARLibMain(HANDLEhlnstance,WORDwDtaseg,
WORDwHeapSize.LPSTRIpszCmdline)
if(wHeapSize!=0)
Unl13ckData(0);
return1;
intFARPASCALexportdemofl(intfarpath,intfar*databuf);
//函数体
for(inti=0;i<l28;i++)
*(data+i)=0;
return0;
intFARPASCALdemof2(intfarpath,intfardata[128])
//函数体
for(inti=0;i<128;i++)
data[i]=0;
return0;
函数demofl()前面的_export限定符直接声明其为输出函数,加此限定符后可不用直接在。
DEF文件EXPORTS段中声明其为输出函数,但此时应用程序只能用输出函数原名调用,而不能使用输出函数的别名或序数名调用。
3)模块定义文件DLLDEMO.DEF
LIBRARYDLLDEMO
DESCRIPTION’DemoDLLProgram’
EXETYPEWINDOWS
CODEPRELOADMOVEABLE
DATAPRELOADMOVEABLESINGLE
HEAPSIZE1024
EXPORTSdemofl
demof2
注意模块定义在文件中的LIBRARY段和DATA段.在BodandC++IDE(集成调试环境)中编辑好以上程序文件后,须先设置OptionsfApplication 选项中的LinkerOutput为WindowsDLL,表明生成DLL库程序;CompilerJCodeGenerationOptions选项中AssumeSSEqualsDS为Never.然后可以按键用Make功能生成DLL库。
第二章调用动态连接库
DLL编写调试好后就可由其它应用程序调用,通常有显式和隐式两种调用方式。
2.1显式调用方式
是由编程者用API函数加载和卸载DLL来达到调用DLL的目的,使用上较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。
程序员可以决定DLL文件何时加载或不加载,显式链接在运行时决定加载哪个DLL文件。
使用DLL的程序在使用之前必须调用LoadLibrary函数或MFC提供的AfxLoadLibrary函数显式地将动态连接库调进来,动态连接库的文件名即是上面两个函数的参数,从而得到一个DLL模块的句柄,然后调用GetProcAddress函数得到输出函数的指针,自此,你就可以象使用本应用程序自定义的函数一样来调用此引入函数了。
在退出之前必须调用FreeLibrary函数卸载DLL。
调用过程如下:
1)调用Win32的LoadLibrary函数,并指定DLL的路径名称作为
参数,返回HINSTANCE(句柄)参数;
2)调用GetProcAddress函数(使用HINSTANCE和需调用的函数符
号名或标识号作为参数),GetProcAddress函数将符号名或标
识号转换为DLL内部的地址,返回函数指针;
3)以返回的函数指针调用该函数。
2.2 隐式调用方式
由编译系统完成对DLL的加载和应用程序结束后对DLL卸载。
程序员在建立一个DLL文件时,链接程序会自动生成个与之对应的LIB导入
文件。
该文件包含了每一个DLL导出数的符号名和可选的标识号,但是并不含有实际的代码。
隐调用需要把产生动态连接库时产生的LIB文件加入到应用程的工程中,想使用DLL中的函数时,只须在头文件中加以说明LIB文件作为DLL的替代文件被编译到应用程序项目中。
当序员通过静态链接方式编译生成应用程序时,应用程序中的用函数与LIB文件中导出符号相匹配,这些符号或标识号进到生成的EXE文件中。
LIB文件中也包含了对应的DLL文件(但不是完全的路径名),链接程序将其存储在EXE文件内部当应用程序运行过程中需要加载DLL文件时,Windows 根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。
所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中。
可执行程序链接到一个包含DLL输出函数信息的输入库文件(LIB文件)。
操作系统在加载使用可执行程序时加载DLL。
可执行程序直接通过函数名调用DLL的输出函数,调用方法和程序内部其他的函数是一样的。
当DLL使用_declspec(dllexport)关键字的方式导出函数时用户程序在调用DLL函数时必须使用_declspec(dllimport)关键字声明导入函数.如_declspec(dllimport)voidfunction(void)当DLL使用DEF文件方式导出函数时,只需在头文件中声明导入函数,不必在函数声明前加上_declspec(dllimport)关键字。
采用显式调用方式用LoadLibrary函数调用DLL,可以指定DLL的路径。
如果没有指定DLL的路径或采用隐式调用方式Windows将遵循下面的搜索顺序来定位DLL:
1)包含EXE文件的目录。
2)进程的当前工作目录。
3)Windows系统目录。
4)Windows目录。
5)列在Path环境变量中的一系列目录。
因此,当调用DLL时,需将DLL文件拷贝到应用程序目录Windows 系统目录下。
但一般不推荐拷贝到Windows系统目录下,以免Windows 的系统目录下文件过多造成混乱而难于管理。
附录
参考文献
1. Microsoft Co. Windows 程序员参考大全清华大学出版社
2. Microsoft Co. Windows 应用程序设计接口清华大学出版社
3.Gunderson, Bob. "Loading, Initializing, and Terminating a DLL."
(MSDN Library Archive)
4.Ruder, Dan. "DLL Anatomy—What's a DLL Made Of?" (MSDN Library
Archive)。