DLL (Dynamic Link Library)

一、引言
动态链接库在Windows中是非常重要的,几乎所有的内容都是以DLL的形式存在的。例如显示的字体和图标存储在GDI.DLL中;显示Windows桌面,处理用户输入所需要的代码被存储在User.DLL中;Windows编程所需要的大量的API函数存储在Kernel.DLL中。
比较大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。有些模块的功能较为通用,在构造其它软件系统时仍会被使用,此时,如果将所有模块的源代码都静态编译到整个应用程序EXE文件中,会产生一些问题:一是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;二是在编写大的EXE程序时,每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。因此,我们可以利用Windows系统平台提供的一种完全不同的编程和运行环境,将独立的程序模块创建为较小的DLL文件,并可对它们单独编译和测试。这样,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中,不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。
二、DLL基本概念
       应用程序要从目标代码(.obj)外部引用函数,可以通过两种途径实现——静态链接和动态链接。
1. 静态链接
       这种方式下,链接程序首先对库文件(.lib)进行搜索,直到在某个库中找到包含函数的对象模块为止。然后,链接程序把这个对象模块拷贝到可执行文件(.exe)中。链接程序负责维护对该函数的所有引用。
       对于Windows而言,静态链接会导致很大的问题。由于Windows是一个多任务的操作系统,会有多个应用程序同时运行,如果有多个应用程序运行相同的静态链接函数,这样在内存中就会有这个函数的多个副本,造成内存的极大浪费。
2. 动态链接
       这种方式下,链接程序同样对库文件(.lib)进行搜索,直到在某个库中找到所引用的函数的输入记录为止。注意,不同于静态链接的对象模块,这里查找的是输入记录,它不直接包含函数的代码或者数据,而是指定一个动态链接库(.dll)。该动态链接库包含函数的函数名(或函数序号)和代码。然后,链接程序把输入记录拷贝到可执行文件中,产生一次对该函数的动态链接。动态链接库主要有如下特点。
  •  系统中,同时运行的多个应用程序可以同时使用同一个动态链接库,它们在内存中共享DLL文件的一个拷贝。这样不仅节省了内存,而且减少了文件的动态交换。
  •  只要编写的应用程序的函数变量、返回值的类型、数量不发生变化,动态链接库的函数可以不用重新编译链接,直接使用。
  • 只要遵循一定的规则,不同语言编写的应用程序可以调用同一个动态链接库。
概括来说,DLL实际上是一种磁盘文件,以.dll、.drv、.fon、.sys和许多以.exe为扩展名的系统文件都可以是DLL。它由全局数据、服务函数和资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分
三、DLL的分类
       Visual C++ 支持三种DLL,它们分别是Non-MFC Dll(非MFC动态库)、Regular Dll(常规DLL)、Extension Dll(扩展DLL)。
1. Non-MFC DLL(非MFC动态库)
这种动态链接库没有采用MFC的类库结构,而是直接用C语言写的DLL。它的导出函数是标准的C接口,能被MFC和非MFC编写的应用程序调用。相比采用MFC类库编写的DLL,它占用很少的磁盘和内存空间。
2. Regular DLL(常规 DLL)
这种动态链接库和下面的Extension DLL一样,是用MFC类库编写的。它的一个明显的特点是,在源文件里有一个继承自CWinApp的类,导出的对象可以是C函数、C++类或者C++成员函数。只要应用程序能调用C函数,它就可以调用常规DLL。因此,该类应用程序可以是由Visual C++、Delphi、Visual Basic、Borland C等编译环境开发的。
3. Extension DLL(扩展 DLL)
       这种动态链接库由MFC的动态链接库版本创建,且只能被使用MFC类库编写的应用程序调用。例如,为了创建一个新的工具栏,创建了一个CToolBar类的派生类,而要导出这个类,必须把它放到一个MFC扩展的DLL中。
       扩展DLL和常规DLL不一样,它没有一个从CWinApp继承迩来的类的对象,所以开发人员必须在DLL中的DllMain函数添加初始化代码和结束代码。
四、DLL的工作原理
       应用程序打开动态链接库时,把动态链接库的执行代码映射到进程的地址空间中,这里的进程包括了使用动态链接库的每一个进程。而动态链接库中的数据,应用程序则不是通过映射方式获取,而是做了一个备份。也就是说动态链接库所有的的执行代码是共享的,但其中的变量,每个应用程序均备份了一份。但也不是所有的数据都如此,有一些数据是为各个进程所共享的。
1. 文件映射
       当应用程序调用动态链接库时,系统首先要为这个动态链接库建立一个文件映射视图,然后搜寻调用者的地址空间,将文件视图映射到进程的地址空间中。如果同时又有另一个应用程序调用这个动态链接库,那么系统只是简单地将这个动态链接库的另一个文件视图映射到这个应用程序的地址空间。使用这个动态链接库的每一个进程,都有这个动态链接库的一个映射视图。
2. 引用表
       将文件映射到位后,系统要检查调用者和动态链接库的引用表,并把每个DLL函数新分配的虚拟地址插入调用者的输入库中。如果系统需要调整动态链接库中的引用表,需要拷贝被修改过的内存页面。由于多个进程调用相同的动态链接库时,这个动态链接库会被映射到不同的虚地址,所以不同的进程对这个动态链接库模块进行了不同的地址修改。
3. 内存分配
       在默认情况下,动态链接库为变量分配的所有内存空间,都是由调用这个动态链接库的进程的堆分配的。如果两个进程同时调用同一个动态链接库,所有这些变量都要被分配两次,也就是说每个进程的地址空间中都要被分配一次。
      Win32系统中,动态链接库没有自己的局部堆,当一个进程装入一个动态链接库时,系统把动态链接库汇总的数据和代码映射到进程的地址空间中。动态链接库中的任何内存分配都是从进程的地址空间中分配的,而任何其他进程都不能访问这块内存,每个进程都有一套只属于自己的动态链接库的全局和静态变量,在多进程中,这些变量是不共享的。

猜你喜欢

转载自blog.csdn.net/yuxiao1121/article/details/2541430