第13章动态链接库13.1DLL基础知识本节讲述DLL的基础知识。13.1.1DLL概述Windows系统平台上提供了一种完全不同的有效的编程和运行环境,可以将独立的程序模块创建为较小的DLL(DynamicLinkableLibrary)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。Windows自己就将一些主要的系统功能以DLL模块的形式实现。13.1.2认清DLL与LIBDLL与LIB的相似之处是它们都是将一部分可执行代码以及数据放在库中供用户程序使用,而且在使用时,这些代码就象是用户程序本身的一部分。LIB文件是静态链接库文件,在其中放置了许多的函数和变量。其中一部分函数和变量是供内部使用的,在接口中不可见,即没有输出;另一部分是供接口使用的(当然内部函数也可以使用),外部可见,从而应用程序可以调用这些输出的函数和变量。在DLL中同样包括许多变量和函数,也分为内部变量、函数和外部接口函数、变量。对于内部的函数和变量,供DLL自己调用,因此并不包含名称,只使用地址;对于供外部调用的函数和变量,在内部的时候也使用地址,只是在头文件中包含了输出变量和函数的名称,而且包含了这些名称所对应的地址,即将名称和地址对应起来。通过这些名称就可以查找对应变量和函数的地址(函数的地址是指函数入口点的地址)。13.1.3认清DLL与EXEDLL和EXE都是Windows下的可执行模块,在对应的文件结构上,它们也类似的:具有文件头,重定位信息表,导入动态库表等,另外,DLL作为供程序调用的服务者,文件中还包含导出的函数表和变量表。DLL是服务的提供者,主要用来提供输出变量和函数供别的程序调用,在DLL被装入的时候,以及进程中创建线程的时候,Windows都会以不同的参数调用入口点函数,然后该函数进行某些初始化工作后返回,DLL的执行就停止了。Windows并不为DLL创建单独的进程空间,而是将其装入共享地址,然后将其映射到不同的进程供进程调用,从而达到代码共享的目的。EXE是DLL所提供服务的使用者,调用DLL中的输出的函数和变量,每一个EXE在运行的时候,Windows均为它创建单独的进程环境,包括进程地址空间,EXE就在它的地址空间中运行,对别的进程是不透明的,因此也就无法为别的进程调用,因此在许多情况下只能使用DLL来实现某些功能。13.1.4DLL的两种动态链接方法加载时动态链接由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码(如还有其它程序使用该DLL,则Windows对DLL的应用记录减1,直到所有相关程序都结束对该DLL的使用时才释放它),简单实用,但不够灵活,只能满足一般要求。运行时动态链接是由编程者用API函数加载和卸载DLL来达到调用DLL的目的,使用上较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。13.2DLL入/出口函数Win32DLL与Win16DLL有很大的区别,这主要是由操作系统的设计思想决定的。一方面,在Win16DLL中程序入口点函数和出口点函数(LibMain和WEP)是分别实现的;而在Win32DLL中却由同一函数DLLMain来实现。无论何时,当一个进程或线程载入和卸载DLL时,都要调用该函数。13.2.1DllMain函数每一个DLL必须有一个入口点,DLLMain是一个缺省的入口函数。DLLMain负责初始化(Initialization)和结束(Termination)工作,每当一个新的进程或者该进程的新的线程访问DLL时,或者访问DLL的每一个进程或者线程不再使用DLL或者结束时,都会调用DLLMain。但是,使用TerminateProcess或TerminateThread结束进程或者线程,不会调用DLLMain。13.2.2MFCAppWizard生成的RegularDLL入/出口MFCAppWizard生成的RegularDLL在后面的章节会有介绍,这里只讨论它的入/出口点问题。每个RegularDLL都有MFCAppWizard自动生成的CWinApp派生类的对象,与其它MFC应用程序一样,它是在CWinApp派生类的成员函数InitInstance和ExitInstance完成初始化和终止的工作。实际上,MFC提供了一个基本的DllMain函数,在这种DLL中不必自己编写DllMain函数,由MFC提供的这个函数在装载DLL的时候调用InitInstance函数,而在DLL退出的时候调用ExitInstance函数。所需要完成的初始化和终止的工作需要在这两个函数中完成。13.3从DLL中导出函数关于DLL的函数动态链接库中定义有两种函数:导出函数(exportfunction)和内部函数(internalfunction)。导出函数可以被其它模块调用,内部函数在定义它们的DLL程序内部使用。13.3.1使用DEF文件导出函数模块定义文件(.DEF)是一个或多个用于描述DLL属性的模块语句组成的文本文件,每个DEF文件至少必须包含以下模块定义语句:第一个语句必须是LIBRARY语句,指出DLL的名字;EXPORTS语句列出被导出函数的名字;将要输出的函数修饰名罗列在EXPORTS之下,这个名字必须与定义函数的名字完全一致,如此就得到一个没有任何修饰的函数名了。可以使用DESCRIPTION语句描述DLL的用途(此句可选);“;”对一行进行注释(可选)。13.3.2使用关键字_declspec(dllexport)使用MFC提供的修饰符号_declspec(DLLexport)在要输出的函数、类、数据的声明前加上_declspec(DLLexport)的修饰符,表示输出。__declspec(DLLexport)在C调用约定、C编译情况下可以去掉输出函数名的下划线前缀。externC使得在C++中使用C编译方式成为可能。在C++下定义C函数,需要加externC关键词。用externC来指明该函数使用C编译方式。输出的C函数可以从C代码里调用。13.3.3使用AFX_EXT_CLASS导出MFC扩展DLL使用AFX_EXT_CLASS来导出类,链接这种DLL的应用程序或其他DLL使用这个宏来导入类。如果用于DLL应用程序的实现中,则表示输出;如果用于使用DLL的应用程序中,则表示输入。要输出整个的类,对类使用_declspec(_DLLexpot);要输出类的成员函数,则对该函数使用_declspec(_DLLexport)。13.4DLL中的数据和内存13.4.1DLL多进程间的数据共享DLL被多个进程调用,它的代码从而被映射到不同的进程内存空间之中,DLL共享数据也尽量只在系统中有一个拷贝,这是通过COPY-ON-WRITE技术实现的。使用共享数据段,首先需要把欲放入数据段的数据定义在特定的数据段中,然后在说明这个数据段是共享数据段就可以了。13.4.2DLL进程中多线程间的数据隔离WindowsAPI提供了进程中多线程数据分离的一系列TLS(ThreadLocalStorage)函数,这是进程内线程隔离的解决方法,TLS函数集共有四个函数:DWORDTlsAlloc(VOID);BOOLTlsFree(DWORDdwTlsIndex);LPVOIDTlsGetValue(DWORDdwTlsIndex);BOOLTlsSetValue(DWORDdwTlsIndex,LPVOIDlpTlsValue);函数TlsAlloc用来分配线程局部变量索引,它的返回值是后面三个函数的参数dwTlsIndex。而函数TlsFree负责释放线程变量索引。所以这两个函数应该在副线程创建之前的主线程中调用。在DLL模块中,就是在入口点函数被参数调用时候使用。线程运行过程中,可以使用函数TlsGetValue和TlsSetValue根据线程变量的索引号存取各自的数据指针,并存取到各自的数据。13.5几种常用的DLLMFC中的DLLa、Non-MFCDLL:指的是不用MFC的类库结构,直接用C语言写的DLL,其输出的函数一般用的是标准C接口,并能被非MFC的应用程序所调用。13.5.1Win32DLLVC6支持自动生成的Win32DLL和MFCAppWizardDLL,其中自动生成的Win32DLL共包括三种DLL工程。从FILE|NEW菜单项,选择对话框中的Projects选项卡,图13.1选中Win32Dynamic-LinkLiabrary,输入工程名,点击OK按钮后,弹出如下对话框图13.2所示:选择所要生成的Win32DLL类型了。13.5.2RegularstaticallylinkedtoMFCDLL在VC6中有三种形式的MFCDLL(在该DLL中可以使用和继承已有的MFC类)可供选择,即RegularstaticallylinkedtoMFCDLL(标准静态链接MFCDLL)和RegularusingthesharedMFCDLL(标准动态链接MFCDLL)以及ExtensionMFCDLL(扩展MFCDLL)。第一种DLL的特点是,在编译时把使用的MFC代码加入到DLL中,因此,在使用该程序时不需要其他MFC动态链接类库的存在,但占用磁盘空间比较大;13.5.3RegularusingthesharedMFCDLL第二种DLL的特点是:在运行时,动态链接到MFC类库,因此减少了空间的占用,但是在运行时却依赖于MFC动态链接类库。动态链接到MFC的规则DLL应用程序里头的输出函数可以被任意Win32程序使用,包括使用MFC的应用程序。但是,所有从DLL输出的函数应该以如下语句开始:AFX_MANAGE_STATE(AfxGetStaticModuleState())此语句用来正确地切换MFC模块状态。13.5.4MFCExtensionDLL第三种DLL的特点类似于第二种,作为MFC类库的扩展,只能被MFC程序使用。ExtensionDLL用来实现从MFC所继承下来的类的重新利用,就是说,用这种类型的动态连接库,可以用来输出一个从MFC所继承下来的类。它输出的函数仅可以被使用MFC且动态链接到MFC的应用程序使用。可以从MFC继承所想要的,更适于用户自己用的类,并把它提供给应用程序。也可随意的给应用程序提供MFC或MFC继承类的对象指针。ExtensionDLL使用MFC的动态连接版本所创建的,并且它只被用MFC类库所编写的应用程序所调用。13.6DLL的调用和调试13.6.1VC对DLL的调用系统运行一个调用DLL的程序时,将在以下位置查找该DLL:1.包含EXE文件的目录2.进程的当前工作目录3.Windows系统目录4.Windows目录5.列在Path环境变量中的一系列目录若找不到该文件,系统将显示对话框提示并终止程序的执行,13.6.2VB对DLL的调用在VB中调用WindowsDLL之前,必须在Standard(代码)模块的Declarations段中加一个特殊的声明。对于一个DLL或API函数,Declare语句是Windows所必需的,这一点很重要。并且在32位版的VB中动态链接库中的函数对条件是很敏感的。13.6.3DLL的调试DLL的调试有很多中方法,但都需要把DLL工程生成的后缀名为.dll的文件放在执行它的应用程序可以找到的目录中。这可以有很多中方法,手工也行的。应用VC可以很容易地调试DLL,只要从DLL工程中运行调试程序即可。当第一次调试DLL的时候,调试程序会跳出如下对话框图13.5。13.7DLL例程13.7.1使用已有的DLL本例程将通过调用DLL来实现程序的隐藏,即不显示程序窗口,程序不显示在任务栏上,在按下Ctrl+Alt+Del键后出现的任务列表中也不显示。首先创建工程:1.在VC+