库文件
1.概论
先来阐述一下DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上经历了“无库-静态链接库-动态链接库”的时代。静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
对动态链接库,我们还需建立如下概念:
(1)DLL 的编制与具体的编程语言及编译器无关
只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL都可以相互调用。譬如Windows提供的系统DLL(其中包括了Windows的API),在任何开发环境中都能被调用,不在乎其是Visual Basic、Visual C++还是Delphi。
(2)动态链接库随处可见
我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这些DLL中。kernel32.dll中的函数主要处理
内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。
一般的程序员都用过类似MessageBox的函数,其实它就包含在user32.dll这个动态链接库中。由此可见DLL对我们来说其实并不陌生。
(3)VC动态链接库的分类
Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLL(MFC规则DLL)、MFC Extension DLL(MFC扩展DLL)。
非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;MFC规则DLL 包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。
2.静态链接库
对静态链接库的讲解不是本文的重点,但是在具体讲解DLL之前,通过一个静态链接库的例子可以快速地帮助我们建立“库”的概念。
用visual studio 创建动态库
如图1,在VC++6.0中new一个名称为libTest的static library工程(单击此处下载本工程),并新建lib.h和lib.cpp两个文件,lib.h和lib.cpp的源代码如下:
//文件:lib.h
#ifndef LIB_H
#define LIB_H
extern \"C\" int add(int x,int y); #endif
//文件:lib.cpp
#include \"lib.h\"
int add(int x,int y)
{
return x + y;
}
//声明为C编译、连接方式的外部函数
编译这个工程就得到了一个.lib文件,这个文件就是一个函数库,它提供了add的功能。将头文件和.lib文件提交给用户后,用户就可以直接使用其中的add函数了。
标准Turbo C2.0中的C库函数(我们用来的scanf、printf、memcpy、strcpy等)就来自这种静态库。
下面来看看怎么使用这个库,在libTest工程所在的工作区内new一个libCall工程。libCall工程仅包含一个main.cpp文件,它演示了静态链接库的调用方法,其源代码如下:
#include #include \"..\\lib.h\" #pragma comment( lib, \"..\\\\debug\\\\libTest.lib\" ) //指定与静态库一起连接 int main(int argc, char* argv[]) { printf( \"2 + 3 = %d\add( 2, 3 ) ); } 静态链接库的调用就是这么简单,或许我们每天都在用,可是我们没有明白这个概念。代码中#pragma comment( lib , \"..\\\\debug\\\\libTest.lib\" )的意思是指本文件生成的.obj文件应与libTest.lib一起连接。如果不用#pragma comment指定,则可以直接在VC ++中设置,如图2,依次选择tools、options、directories、library files菜单或选项,填入库文件路径。图2中加红圈的部分为我们添加的libTest.lib文件的路径。 在VC中设置库文件路径 这个静态链接库的例子至少让我们明白了库函数是怎么回事,它们是哪来的。我们现在有下列模糊认识了: (1)库不是个怪物,编写库的程序和编写一般的程序区别不大,只是库不能单独执行; (2)库提供一些可以给别的程序调用的东东,别的程序要调用它必须以某种方式指明它要调用之。 以上从静态链接库分析而得到的对库的懵懂概念可以直接引申到动态链接库中,动态链接库与静态链接库在编写和调用上的不同体现在库的外部接口定义及调用方式略有差异。 静态库 目前以lib后缀的库有两种,一种为静态链接库(Static Libary,以下简称“静态库”),另一种为动态连接库(DLL,以下简称“动态库”)的导入库(Import Libary,以下简称“导 入库”)。 静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。比如你链接一个静态库,如果其中有错,它会准确的找到是哪个obj有错,即静态lib只是壳子。 动态库一般会有对应的导入库,方便程序静态载入动态链接库,否则你可能就需要自己LoadLibary调入DLL文件,然后再手工GetProcAddress获得对应函数了。有了导入库,你只需要链接导入库后按照头文件函数接口的声明调用函数就可以了。 导入库和静态库的区别 导入库和静态库的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。 这也是实际上很多开源代码发布的惯用方式: 1. 预编译的开发包:包含一些.dll文件和一些.lib文件。其中这里的.lib就是导入库,而不要错以为是静态库。但是引入方式和静态库一样,要在链接路径上添加找到这些.lib的路径。而.dll则最好放到最后产生的应用程序exe执行文件相同的目录。这样运行时,就会自动调入动态链接库。 2. 用户自己编译: 下载的是源代码,按照readme自己编译。生成很可能也是.dll + .lib(导入库)的库文件 3. 如果你只有dll,并且你知道dll中函数的函数原型,那么你可以直接在自己程序中使用LoadLibary调入DLL文件,然后使用GetProcAddress调用DLL中的函数。 当DLL被链接时,链接程序要查找关于输出变量,函数,或C++类的信息,并自动生成一个lib文件。该lib文件包含一个DLL输出的符号列表。如果要链接引用该DLL的输出符号的任何可执行模块,该lib文件是必不可少的(使用GetProcAddress除外)。 其实导入库中并不含RVA(每个符号的相对虚拟地址),只是一些符号而已,还有关于这个lib所对应的DLL的名字等。 (这只是我现在的理解) 那当应用程序调用一个DLL的函数时,是怎么进行的呢?(使用lib的情况下) 答案是在进程的主线程开始运行之前,由加载器完成。 加载器根据输入节中DLL的名字按照windows的搜索路径搜索DLL,找到后DLL映射到进程的地址空间,这是DLL中对应于输入节中的各个符号的地址就可以确定了,加载器在这个时候将地址重新填入可执行模块的输入节中,动态连接完成。 DEF文件的写法: LIBRARY BTREE // 库名为BTREE,省略库名即为动态链接库文件名 EXPORTS // 输出 Insert @1 // 带序号的输出函数名 Delete @2 Member @3 Min @4 Dynamic-Link Libraries 概述 Microsoft Windows DLL程序是包函数和数据的模块。由调用它的其它模块(EXE或DLL)动态加载。加载时,它被映像到调用模块的地址空间内。 DLL可以定义两类函数:输出函数和内部函数。输出函数可以被其它模块调用。可以定义输出数据,但输出数据一般只由它自己的函数使用。 DLL有助于应用程序实现模块化。当多个应用程序使用相同的模块时,还能减少内存占用,原因是每个应用程序会有自己的数据复本,DLL的代码是共享的。 动态链接允许模块只包含装载时或运行时系统需要的信息,这点与静态链接不同。静态链接时系统会为每个调用模块拷贝一份相应的函数代码。 每个被装载的DLL都由系统设置一个计数标志。某个线程使用DLL时,系统将这个计数值加1,线程结束时,计数值减1。当计数值减到0时,系统将其从调用模块的虚拟空间中卸载。 使用DLL需要有如下规定: 1.装载DLL的线程可以使用该DLL打开的句柄。同样,调用模块的线程打开的其它句柄也可以由DLL使用。 2.DLL使用调用线程的堆栈。 3.DLL参考调用进程的虚拟内存定位地址。 动态链接形式 使用DLL有两种方法: 1.启动装载动态链接。模块直接调用DLL输出函数。调用模块事先必须用输入函数表连接到DLL及其使用的其它DLL或库。模块的输入函数表会指导操作系统,在应用程序装载时需要的DLL函数的信息和定位方式。 2.运行装载动态链接。调用模块用LoadLibrary或LoadLibraryEx函数在运行时刻装载DLL。调用模块用GetProcAddress函数等到DLL函数的句柄,用这个句柄调用其函数。这种方法不用输入函数表。 动态链接的优点 相比静态链接,动态链接有几个优点: 1.多个进程在同一基地址装载相同DLL,只需要一个共享DLL即可,它在物理地址空 间内共享。 2.DLL内的函数变化,只要其输入输入参数、调用格式没有变,应用程序自身不必重新编译。静态链接需要重新编译。 3.DLL提供异步支持。比如应用程序装载DLL时,用DLL支持一个无效设备。 4.不同语言编写的应用程序可以用约定的调用格式调用DLL函数。 潜在缺点是应用程序不是相对独立的。启动装载动态链接时,DLL不存在,程序不被装载。运行装载时,如果DLL不存在,由程序处理。 创建动态链接库 创建DLL需要一个或多个源文件,可能需要输出函数用的连接文件。如果是启动装载的DLL,还要创建输入库。源文件内是输出函数和内部函数,可选的DLL入口函数。DLL由多线程的应用程序使用,要将DLL连接成thread-safe类型,以支持多线程,也要同步全局数据。 输出函数的定义是需要编译程序支持的,声明方式也不相同。使用MS VC++,有两种定义输出函数的方式:源文件中使用_declspec 定义,或用.DEF文件定义。 输入库(.LIB)文件包含连接程序需要的DLL输出函数,系统定位指定的DLL及其输出函数。VC++中要指定该DLL的输入库文件(.LIB),才能编译使用该DLL中的函数。 DLL入口函数 每个DLL必须有入口点,并不必须有入口函数,入口点由连接程序指定。应用程序装载或卸载DLL时系统会调用入口函数。一般在这里进行初始化工作。参看DllEntryPoint函数,用户可以更改这个函数名,也要参看连接程序文档。调用入口程序的事件: 1.装载DLL后。 2.卸载DLL之前。 3.进程创建了一个新线程。使用DisableThreadLibraryCalls函数会取消这个动作。 4.线程正常结束,不是使用TerminateThread或TerminateProcess函数。 同一时刻只有一个线程允许调用DLL入口函数。DLL可以使用TLS(线程局部存贮表)获得线程信息。定义格式: BOOL WINAPI DllEntryPoint( HINSTANCE hinstDLL, // handle to DLL module DWORD fdwReason, // reason for calling function LPVOID lpReserved ) // reserved 函数开头处,可以获得系统传递的调用信息: A process loads the DLL (DLL_PROCESS_ATTACH). The current process creates a new thread (DLL_THREAD_ATTACH). A thread exits normally (DLL_THREAD_DETACH). A process unloads the DLL (DLL_PROCESS_DETACH) 不定义入口函数,装载时会产生错误。入口函数操作成功应该返回TRUE。 启动装载DLL时的搜索路径 1.启动模块路径。 2.当前路径。 3.Windows系统路径。GetSystemDirectory函数可以返回这个路径。 4.Windows路径。GetWindowsDirectory函数返回这个路径。 5.PATH环境路径。 运行装载DLL 不同路径的重名DLL不会认为是相同的DLL。 LoadLibrary 或 LoadLibraryEx函数会调用DLL入口函数。 GetModuleHandle 函数返回DLL句柄。GetProcAddress函数获得地址。运行时加载DLL,在DLL出错时,应用程序仍可运行,但不能再使用该DLL中的函数。 DLL的数据 DLL缺省的变量使用范围与应用程序相同。DLL中的全局变量在调用进程看来仍是全局变量。静态变量在定义的块内使用。默认情况下,每一个调用进程都有DLL全局和静态变量实例。定义方法与编译程序有关。DLL使用GlobalAlloc, LocalAlloc, HeapAlloc, 和 VirtualAlloc函数操作内存时,分配的内存空间在调用进程的虚拟内存空间之内。DLL需要使用File Mapping方式在进程间共享内存。参看file mapping。 定义输出函数 使用MS VC++,在DLL头文件中,能够定义输出类,输出函数或变量。类直接定义,输出函数和变量用extern说明。 在动态链接库中使用共享内存 使用共享内存的技术叫做File Mapping。通过File Mapping对象设置内存,进程可以和其装载的DLL通讯。DLL共享内存只在DLL装载期间有效。将共享内存映像到使用DLL的每个进程的虚拟地址空间,入口函数需要下列处理: 1.调用CreateFileMapping函数取得一个File-mapping对象句柄,第一个装载DLL的进程创建这个对象。其它进程打开已经存在的对象。参看Creating a File-Mapping Object。 2.调用MapViewOfFile函数映像一个复本(view)到虚拟空间。这样使进程可以访问这块共享内存。参看Creating a File View。 共享内存映像到不同进程的时候可能有不同的虚拟地址。所以不同进程有它自己的指向内存的指针。最后使用File mapping 对象关闭时,共享内存会关闭。 因篇幅问题不能全部显示,请点此查看更多更全内容