1.6 建立和使用动态链接库
以上我们分析了应用程序实例STLViewer的模块化层次结构。这是一个由多个动态链接库组成的、模块化的CAD软件。本节将介绍动态链接库的一些基本概念、使用方法,以及利用Visual C++开发动态链接库的有关知识。在第2章中将尝试创建第一个DLL——几何工具类库GeomCalc.dll。GeomCalc.dll将输出开发CAD系统需要的基础几何表示类,如点、矢量和矩阵,以及一些常用的计算函数。通过对GeomCalc.dll的开发,相信读者能够掌握开发DLL的有关技术。同时,通过几何基础类的设计和开发,读者也会对使用Visual C++设计与开发中的一些技术细节有更深刻的认识。
1.6.1 动态链接库的基本概念
在软件结构中,库(library)是指一个或多个目标文件(.obj文件)经过组合而形成的一个代码群,这些目标文件经过链接后生成一个可执行文件。它可以简单地被看成是一种仓库,提供一些可以直接拿来用的变量、函数或类。例如在本项目中,将功能相对集中、易于重复使用的代码根据其功能分别设计了基础几何计算库(GeomCalc.dll)、CAD几何模型(GeomKernel.dll)、OpenGL图形绘制功能(glContext.dll)、界面工具函数(DockTool.dll)四个库。
与库的链接方式有两种:静态链接与动态链接。静态链接库与动态链接库都是共享代码的方式。在静态链接中,无论你愿不愿意,链接程序将所需求的目标代码从库文件中复制到可执行文件EXE中。之所以被称为“静态”,是因为对库中的所有函数调用在链接生成可执行文件时已经完成,运行可执行文件时,不需要再从库中调用目标代码。这样链接生成的执行文件的尺寸会比较大。而动态链接时,链接程序并没有把所需要的目标代码从库文件中复制到可执行文件中,而是可执行文件在运行的开始或运行的过程中,根据需要从库文件中装载并使用相应目标代码,在使用完后可以根据需要及时卸载,从而释放不必要的系统资源。之所以被称为动态链接,是因为对目标代码的调用不是在链接时完成的,而是在执行过程中从库文件中动态装载使用的。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态链接库,而在动态链接库中还可以再包含其他的动态或静态链接库。
动态链接库(DLL)即是这样一种动态链接模块,是包含输出类和共享函数库的二进制文件,可以被多个程序同时使用。建立应用程序的可执行文件时,不必将DLL链接到程序中,而是在运行时动态装载DLL,装载时DLL被映射到调用进程的地址空间中。因而,DLL是“运行时”的模块。
利用动态链接库技术,有利于应用程序的模块化,可将一个大的应用程序分成多个单独的功能模块,这样也有利于开发队伍之间的分工协作。对大型的CAD软件,如果不采用静态链接技术,将所有的执行代码都加入到执行文件中,会导致程序的主执行文件异常庞大,执行时占用大量的内存而严重影响程序的运行效率。DLL对于Windows系统的使用者来说其实并不陌生。事实上,如果不使用动态链接技术,微机的内存恐怕连Window操作系统本身都启动不了。如果我们浏览Windows目录下的system32文件夹,就会看到诸如kernel32.dll、user32.dll、gdi32.dll等大量的DLL库文件,Windows的大多数API都包含在这些DLL中。例如,kernel32.dll中的函数主要处理内存管理和进程调度,user32.dll中的函数主要控制用户界面,gdi32.dll中的函数则负责图形方面的操作。很多程序员都用过类似MessageBox的函数,其实它就包含在user32.dll这个动态链接库中。
除了节省程序运行时的资源消耗,DLL的另一个主要特点是一个DLL可以同时被多个应用程序共享。在开发软件时,如果能够将一些功能集中、可重复利用率高的类和函数组合成一个独立的模块,则不仅一个系统的其他模块可以调用这些类和函数,它们也可以供别的应用程序共享。也就是说,在STLViewer中开发的四个DLL库,还可用于其他的应用程序。CAD领域著名的几何内核软件ACIS就是应用DLL技术的典型例子,它所提供给用户的就是一系列功能相对集中的DLL库,由用户在这些库的基础上开发自己的CAD应用程序。
另外,如果不改变DLL的接口,即使DLL库发生改变,也不需要重新编译所有调用它的应用程序。这也在一定程度上减轻了软件开发和维护的工作量。
另外,需要强调的是,DLL的设计制作与具体的编程语言和编译器无关。只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL都可以相互调用。譬如Windows提供的系统DLL(其中包括了Windows的API),在任何开发环境中都能被调用,无论是Visual Basic、Visual C++还是Delphi。
1.6.2 基于MFC的动态链接库
使用Visual C++能够建立一个没有MFC库的纯Win32 DLL,正如可以建立一个没有MFC支持的Windows程序一样。然而,本书的所有案例都是基于MFC的,因此这儿只着重介绍基于MFC的DLL库。
MFC提供了三种不同的方式支持DLL的开发:
● 建立静态链接MFC的常规DLL(Regular DLL)。
● 建立动态链接MFC的常规DLL。
● 建立动态链接MFC的扩展DLL(Extension DLL)。
扩展DLL和常规DLL的区别在于:
(1)MFC的扩展DLL支持C++接口,即扩展DLL能够导出整个C++类。这就是说,可以从已有的MFC类派生新的可再用类。扩展DLL在建立时使用的是MFC的动态链接,因而扩展DLL要求客户程序动态地链接到MFC动态库。另外,需要保证客户程序与DLL所链接的DLL库版本保持一致。如果客户程序是基于Visual C++ 2005创建的MFC程序,其本身所链接的MFC库的版本是mfc80.dll;而在Visual C++ 6.0创建的扩展DLL,其被客户程序加载时会动态链接到mfc42.dll,会造成程序运行时的冲突。此外,扩展DLL的一个特点是尺寸很小,可以建立一个大小仅为10KB的简单的扩展DLL,能够很快被系统加载。例如,本书所创建的DLL库均为扩展DLL库,其每个库的尺寸分别为:几何模块(GeomCalc.dll)30KB、图形模块(glContext.dll)60KB、内核模块(GeomKernel.dll)36KB,界面模块(DockTool.dll)34KB。
(2)常规DLL可被任意Win32编程环境(例如Visual Basic)加载。它的局限性在于常规DLL只能导出标准C接口,不能导出C++类、成员函数或重载函数。这是因为每个编程环境编译器有其自己的修饰名方法。但在常规DLL内部,仍然可以使用C++类及MFC类。
(3)常规DLL能够采用显式链接或隐式链接,而扩展DLL只能采用显式链接。
静态链接MFC的DLL与动态链接MFC的DLL的区别在于:
(1)静态链接MFC的DLL将复制所有需要的MFC的代码,成为自我包含的模块,可以独立于MFC的DLL库而运行,但DLL的代码尺寸会较大。
(2)动态链接MFC的DLL在运行时动态链接MFC类库,因而DLL的代码尺寸会大大减小,但必须确保在运行的系统上有合适版本的MFC DLL库。
由以上分析可知,因为需要导出一系列C++类,且很多类还需要从MFC类派生,因而,在本书的实例中要创建的是MFC的扩展DLL。
1.6.3 查看执行程序EXE与DLL库的层次关系
为了便于理解可执行文件EXE与DLL之间的层次关系及调用关系,可以借助Visual Studio提供的辅助工具Dependency Walker(Depends.exe)查看并分析一个程序与其他动态链接模块之间存在的依赖关系与函数调用接口。运行Depends.exe后,选择“File|Open”命令,选择要查看的EXE或DLL文件,则界面中会显示DLL调用的DLL及其提供的接口。如图1-9所示,打开位于本书附带光盘“CH1\”子目录下的主执行文件STLViewer.exe,在左边的树形视图显示了程序所调用的DLL资源列表。可以看到STLViewer.exe的运行直接依赖于八个DLL库。其中,上面四个库(GEOMCALC.DLL、GEOMKERNEL.DLL、Glcontext.DLL、DOCKTOOL.DLL)是由本项目生成并链接的DLL库,而下面四个库(MFC80U.DLL、MSVCR80.DLL、KERNEL32.DLL、USER32.DLL)则是为基于MFC开发的Windows程序而自动链接的系统库。在左边树形视图中,选择相应的DLL,在右边的列表部分则会显示对应的接口函数。其中,Ordinal列表示函数在DLL中的序号或名称,Hint列表示接口函数在DLL内部的序号值,Function列表示函数名称。底部的列表视图显示了调用的DLL模块的信息。从图1-9中可以看到,GEOMCALC.DLL中提供了CBox3D、CPoint3D等输出类供主程序STLViewer.exe使用。
如图1-9所示,DLL库之间也存在层次依赖关系。利用Dependency Walker工具有助于我们了解一个DLL库与其他DLL模块之间存在的层次结构。例如,在图1-9所示的界面左侧的树形视图中打开GEOMKERNEL.DLL(本项目的CAD模型库),则可以看到GEOMKERNEL.DLL与本项目的其他两个模块GEOMCALC.DLL(基础几何计算库)、Glcontext.DLL(OpenGL图形绘制功能库)之间存在依赖关系。
图1-9 用Dependency Walker工具查看程序与DLL之间的函数调用关系