DLL(Dynamic Link Library,动态连接库)是一个可以被其他应用程序调用的程序模块,其中封装了可以被调用的资源或函数。动态连接库的扩展名一般是DLL,但有时也可能是其他扩展名。DLL文件属于可执行文件,符合要求Windows系统的PE文件格式,但它依附于EXE文件创建的过程不能单独运行。DLL多个过程可以装载和调用文件。
Windows操作系统下有很多操作系统DLL有些文件是操作系统的DLL文件,一些应用程序DLL文件。使用DLL文件有什么好处?DLL是动态连接库,对应,有静态连接库。动态连接库在EXE文件运行时加载执行,静态连接库为OBJ文件同时保存在程序中。动态连接库可以减少可执行文件的体积,必要时进入内存;软件分为多个模块,可以根据模块开发,也非常方便发布和升级。必须在某些情况下使用DLL完成一些工作内容。
本文通过一个简单的方式DLL初步了解程序DLL编程。
1. 写简单DLL程序
从一个简单的开始DLL程序开始,并在DLL在程序中添加导出函数。所谓导出函数,就是DLL提供给外部EXE或其他类型的可执行文件调用函数。DLL也可以自己调用。
DLL程序的入口函数不是main()函数,也不是WinMain()函数,但是DllMain()函数的定义如下:
参数说明如下。
hinstDLL:该参数为当前 DLL 模块的句柄,即本动态连接库模块的实例句柄。
fdwReason:表示 DllMain()函数调用的原因。
这个参数有四值,即有四种调用DllMain()函数的情况,这4个值分别是DLL_PROCESS_ATTACH(当DLL在加载过程中,DllMain()调用函数),DLL_PRO CESS_DETACH(当DLL卸载过程时,DllMain()调用函数),DLL_THREAD_ATTACH(当有线程在过程中创建时,DllMain()调用函数)和DLL_THREAD_DETACH(当有线程结束时,DllMain()调用函数)。
lpvReserved:保留参数,即程序员不使用的参数。
启动VC6创成开发环境,创造DLL工程。创建一个“A simple DLL Project”工程类型,VC生成代码如下:
函数定义在生成的代码中有一个APIENTRY函数修饰符。修饰符为宏,定义如下:
由于DllMain()函数不止一次被调用,不同的代码需要根据调用情况执行,例如当过程加载时DLL可能在文件中DLL申请一些资源;卸载DLL此时,您需要释放您以前申请的资源。出于各种原因,在编写中DLL程序,需要把DllMain()函数的结构写成以下形式:
这是一个switch/case结构可以根据不同的调用原因执行不同的代码。
2. 给DLL添加一个简单的导出函数
上面的代码只是一个简单的代码DLL程序的开始没有实际意义。DLL文件来说,DllMain()不必要。DLL文件的本质是为其他可执行文件提供使用,因此DLL该程序需要编写能够为其他程序使用的函数,称为导出函数。在上述代码的基础上添加导出函数,定义如下:
extern "C"表示函数为 C 导出 *** 。因为源代码是.CPP因此,如果是 文件,C 如果导出,编译后函数名称会被名称粉碎,动态调用函数极不方便。__declspec(dllexport)它的作用是声明导出函数,从本 DLL 向其他模块开放。
MsgBox()函数的实现如下:
当函数被调用时,它会被调用MessageBox其过程名显示在窗口的标题栏中。
这样,之一个DLL文件的编写已经完成。编译连接代码,查看编译和连接的输出VC共生成两个文件“FirstDll.dll”和“FirstDll.lib”,前者是供其他可执行程序使用的DLL该文件包含程序员编写的代码和导出函数,后者是一个库文件,包含一些导出函数的相关信息供调用DLL编译时使用导出函数函数的程序员。
导出DLL函数有两种 *** ,其中之一。另一种 *** 是建立一个.DEF定义导出哪些函数的文件。函数不仅可以通过函数名导出,还可以通过序号导出。.DEF文件管理更方便DLL项目中的导出函数(总比在代码中逐一找到)__declspec(dllexport)方便多了)。因为这里的代码比较短,所以用了。__declspec(dllexport)这种定义 *** 。
3. 对DLL调用程序的 *** 1
DLL程序不能单独运行,需要写一个EXE程序(当然也可以是另一个程序)DLL调用此程序DLL导出函数在文件中。VC在综合开发环境中添加测试项目,并在工作区域添加测试项目“Workspace ‘FirstDll’:1 project(s)”单击右键,选择弹出菜单“Add New Project to Workspace”,如图1所示。
图1 添加对DLL测试项目
添加控制台项目,然后编写正确的项目DLL调用的测试代码如下:
#pragma comment (lib,"FirstDll")告诉连接器需要在那里FirstDll.lib文件中找到DLL导出函数的信息。
编译连接上述代码,VC如图2所示,会产生连接错误。
图2 连接错误信息
这个错误是因为连接器找不到“FirstDll.lib”文件。将“FirstDll.lib”将其复制到测试项目目录中,然后添加到测试项目中,然后再次编译和连接。操作编写的测试程序会弹出错误的对话框,如图3所示。
图3 运行测试程序时的错误信息
根据错误的提示,可以看出缺乏测试DLL文件,也就是“FirstDll.dll”文件。将其复制到与可执行文件相同的目录中,然后再次运行,程序可以顺利执行。
一般在发布DLL需要将文件DLL文件、Lib文件和.h当然,如果文件同时发布,文档或手册会更专业。
4. 对DLL调用程序的 *** 二
前一种 *** 是通过连接器静态调用DLL函数的导出函数写入可执行文件。现在用第二种 *** 调用DLL与前一种 *** 相比,该 *** 是动态调用。动态调用不是在连接过程中完成的,而是在运行过程中完成的。动态调用不会写在可执行文件中DLL相关信息。现在我们来写一个关于动态调用的测试程序。该程序的创建 *** 与静态调用 *** 相同不再重复。
动态调用DLL函数代码如下:
编译和连接代码是正常的。但请注意,这个程序没有使用#pragma comment()指令未通过lib在程序中留下相关的导入信息。程序将提示操作编译连接程序“FirstDll.dll文件不存在”。按照前面的 *** FirstDll.dll将文件复制到与测试程序相同的目录下,运行测试程序,并成功执行程序。
DLL动态加载调用非常有用。如果在之一个测试程序中找不到测试系统的装载器DLL文件,系统会直接报错退出。在第二个测试程序中,如果找不到测试程序DLL对于文件,程序会给出错误的提示,程序可以在不影响其他代码运行的情况下继续执行(当然,因为DLL无法加载可能会损失部分的功能)。明白了动态加载调用和静态加载调用的区别,那么它们的优缺点就很清楚了。静态加载调用使用方便,而动态加载调用灵活性较好。
在某些情况下,必须采用动态加载调用的 *** DLL导出函数。例如函数OpenThread(),函数在VC6自带的PSDK中没有提供LIB没有文件和函数原型的定义LIB文件无法成功连接(在新版本中PSDK函数对应LIB文件)。在这种情况下,只能使用LoadLibrary()和GetProcAddress()动态加载和调用这两个函数OpenThread()函数(实际上在很多情况下使用DLL文件中的导出函数找不到相应的LIB例如,文件ntdll.dll虽然导出了许多函数,但系统没有提供相应的函数LIB文件)。
现在了解一下LoadLibrary()函数和GetProcAddress()函数的定义。LoadLibrary()函数的定义如下:
该函数只有一个参数,即要加载的DLL文件名称。如果函数调用成功,则返回模块句柄。
GetProcAddress()函数的定义如下:
函数有以下两个参数。
hModule:该参数为模块句柄,通常通过 LoadLibrary()函数或 GetModuleHandle()函数获得;
lpProcName:该参数指定了函数地址的函数名称。
该函数调用成功,则返回lpProcName函数名的函数地址。
5. 查看DLL介绍程序导出函数的工具
前面介绍DLL编程中提到了导出函数,这里有两个查看DLL程序的导出函数的工具。其中一款是VC自带的工具“Depends”,另一个工具是一个功能更强大的工具,可以用来查看PE识别壳体信息的结构和工具“PEID”。
首先用“Depends”来查看DLL该工具可以在导出函数中VC6在安装菜单下找到,具 *** 置是“开始”→“程序”→“Microsoft Visual Studio 6.0”→“Microsoft Visual Studio 6.0 Tools”→“Depends”。打开程序,依次单击菜单项“File”→“Open”,在“打开”找到对话框中写的FirstDll.dll选择并打开文件(也可直接拖动),显示在工作窗口FirstDll.dll如图4所示。
图4 Depends显示界面
图4右下角区域显示DLL从图4中可以看出文件导出的函数,FirstDll.dll文件只导出一个MsgBox函数。
对于Depends介绍太多了。现在让我们看看另一个工具“PEID”。该工具用于识别软件“指纹”信息(开发环境、版本、外壳信息等)。FirstDll.dll文件拖曳到PEID界面上,PEID会自动分析这一点DLL文件的PE界面如图5所示的结构信息。
图5 PEID显示界面
从图5可以看出,PEID底部的只读编辑框显示FirstDll.dll文件是由VC6而且版本是Debug版本。单击“子系统”右边的“大于号”按钮,会显示PE如图6所示。
图6 PE结构详情
在图6中的PE结构详细信息的下半部分有一个“目录信息”,之一个目录信息是导出表信息,单击“导出表”最右侧的“大于号”按钮,出现“导出查看器”如图7所示。
图7 导出检查器
从图7可以看出,FirstDll.dll只有一个导出函数文件MsgBox(),只有一个导出项。导出函数的信息和Depends相同。