实验五 动态链接库的建立与调用
一、实验目的
- 理解动态链接库的实现原理。
- 掌握Windows系统动态链接库的建立方法。
- 掌握Windows环境下动态链接库的调用方法。
二、实验准备
动态连接库介绍:
动态链接库(Dynamic Link Library DLL)是一个可执行模块,它包含的函数可以由Windows应用程序调用以提供所需功能,为应用程序提供服务。
- 动态链接库基础知识
大型的应用程序都是由多个模块组成的,这些模块彼此协作,已完成整个软件系统的工作。其中可能有些模块的功能是通用的,被多个软件系统使用。在设计软件系统时,如果将所有模块的源代码都静态编译到整个应用程序的.exe文件中,会产生两个问题,一是应用程序过大,运行时消耗较大的内存空间,造成系统资源的浪费;二是在修改程序时,每次程序的调整都必须编译所有的源代码,增加了编译过程的复杂度,也不利于阶段性的模块测试。
Windows系统提供了非常有效的编译和运行环境,可以将独立的模块编译成较小的动态链接库文件,并可对这些动态链接库单独进行编译和测试。运行时,只有在主程序需要时才将动态链接库装入内存并运行。这样不仅减少了应用程序的大小及对内存的大量需求,而且使得动态链接库可以被多个应用程序使用,从而充分利用了资源。Windows系统中的一些主要系统功能都是以动态链接库的形式出现的,如设备驱动器等。
动态链接库文件在Windows系统中的扩展名为.dll,它由全局数据结构、若干函数组成,运行时被系统加载到进程的虚拟地址空间中,成为调用进程的一部分。
如果与其他的动态链接库没有冲突,该文件通常映射到进程虚拟地址空间地址上。
如何建立自己的动态链接库?
每个动态链接库必须有一个入口点,像用C语言编写其他应用程序时必须有一WinMain()函数一样,在Windows系统的动态链接库中,DllMain()是默认的入口函数。
- DllMain()函数原型及参数
BOOL APIENTRY DllMain(HANDLE hModule, //参数hModule为动态链接库的句柄,其值与动态链接库的地址相对应。
DWORD ul_reason_for_call, //参数ul_reason_for_call指明系统调用该函数的原因。
LPVOID lpReserved //lpReserved说明动态链接库是否需要动态加载或卸载。
)
{
return TRUE;
}
NOTE:DllMain()函数不仅在将动态链接库加载到进程地址空间时被调用,在动态链接库进程分离时也被调用。
- 使用入口函数还能使动态链接库在被调用时自动做一些初始化工作,如分配额外的内存或其他资源。
- 在撤销时做一些清除工作,如回收占用的内存或其他资源。
- 需要做初始化或清除工作时,DllMain()函数格式如下:
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved )
{
Switch (ul_reason_for_call){
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
- 初始化或清除工作分以下几种情况。
- DLL_PROCESS_ATTACH
当动态链接库被初次映射到进程的地址空间时, 系统将调用该动态链接库的DllMain()函数,给它传递参数ul_reason_for_call,的值DLL_PROCESS_ATTACH。当处理DLL_PROCESS_ATTACH时,动态链接库应执行动态链接库函数要求的任何与进程相关的初始化工作,如动态链接库堆栈的建立等。当初始化成功时,DllMain()返回TRUE,否则返回FALSE,并终止整个程序的执行。
- DLL_THREAD_ATTACH
当在一个进程中创建进程时, 系统查看当前映射进程的地址空间中的所有动态链接库文件映像,并调用每个带有DLL_THREAD_ATTACH 值的DllMain()函数文件映像。这样,动态链接库就可以执行每个线程的初始化操作。新创建的线程负责自行动态链接库的所有DllMain()函数中的代码。
当一个新动态链接库被映射到进程地址空间时,如果该进程内已经有若干个线程正在执行,那么系统将不为现有的线程调用带DLL_THREAD_ATTACH值的DllMain()函数。只有当新线程创建,动态链接库被映射到进程地址空间时,它才可以调用带有DLL_THREAD_ATTACH值的DllMain()函数。另外,系统并不为主线程调用带DLL_THREAD_ATTACH值的DllMain()函数。进程初次启动时映射到进程的地址空间中的任何动态链接库均接收DLL_PROCESS_ATTACH通知,而不是DLL_THREAD_ATTACH通知。
- DLL_THREAD_DETACH
终止线程的方法是系统调用ExitThread()函数撤销该线程,但 如果ExitThread()函数要终止动态链接库所在的线程,系统不会立即将该线程撤销,而是取出这个即将被撤销的线程,并让它调用已映射的动态链接库中所有带有DLL_THREAD_DETAC值的DllMain()函数。 通知所有的动态链接库执行每个线程的清除操作,只有当每个动态链接库都完成了对DLL_THREAD_DETACH通知的处理时,操作系统才会终止线程的运行。如果当动态链接库被撤销时任然有线程在运行,那么带有DLL_THREAD_DETACH值的DllMain()函数就不会被任何线程调用。所以在处理DLL_THREAD_DETACH时,要根据具体情况进行。
- DLL_PROCESS_DETACH
当动态链接库从进程的地址空间被卸载时, 系统将调用该动态链接库的DllMain()函数,给它传递参数ul_reason_for_call的值DLL_PROCESS_DETACH。当处理DLL_PROCESS_DETACH时,动态链接库执行与进程相关的清除操作,如堆栈的撤销等。
创建动态连接库
#include "stdafx.h"
extern "C"_declspec(dllexport) int Add (int x,int y); //dllexport表示要导出的函数
extern "C"_declspec(dllexport) int Sub (int x,int y);
BOOL APIRNTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call,LPVOID lpReserved)
{
return TRUE;
}
int Add(int x,int y)
{
int z;
z=x+y;
return z;
}
int Sub(int x,int y)
{
int z;
z=x-y;
return z;
}
.dll是编译生成的动态链接库可执行文件,.lib 就是导入/导出库文件,该文件中包含.dll文件名和.dll中的函数名Add()和 Sub(),.lib 是文件.dll的映像文件
静态加载
CWinAPP theApp;
extern "C"_declspec(dllimport) int Add(int x,int y);
extern "C"_declspec(dllimport) int Sub(int x,int y);
using namespace std;
int _tmain(int argc,TCHAR* argv[],TCHAR* envp[])
{
int nRetCode = 0;
int x=7;
int y=6;
int t add=0;
int sub=0;
printf("Call Dll Now!\n");
//调用动态链接库
add=Add(x,y);
sub=Sub(x,y);
printf("7+6=%d,7-6=%d\n",add,sub);
return nRetCode;
}
NOTE:为了能够使用调用程序.cpp正确地调用到动态链接库.dll文件,在生成工程文件.cpp的执行文件之前,先将.dll文件复制到工程文件的Debug目录下,将.lib文件复制到.cpp所在目录下
动态加载
Int_tmain(int argc,TCHAR*argv[],TCHAR*envp[])
{
int s;
int nRetCode=0;
typedef int (*pAdd)(int x,int y);
typedef int (*pSub)(int x,int y);
HMODULE hDll;
pAdd add;
pSub sub;
hDll=LoadLibrary("simpledll.dll"); //动态加载动态链接库simple.dll
if(hDll = NULL)
{
printf("LoadLibrary Error.\n");
return nRectCode;
}
else print("LoadLibrary Success.\n")
add=(pAdd)GetProcAddress(hDll,"Add"); //得到动态链接库内部函数Add()的地址
s=add(6,2);
printf("6+2=%d\n",s);
sub=(pSub)GetprocAddress(hDll,"Sub"); //得到动态链接库内部函数Sub()的地址
s=sub(6,2);
printf("6-2=%d\n",s);
FreeLibrary(hDll); //动态释放动态链接库dimpedll.dll
Return nRetCode;
}
使用类型定义关键字typedef定义了指向动态链接库中相同函数原型的指针,然后通过LoadLibrary(“dll.dll”)将到头了即可文件SimpleDll.dll加载到应用程序中,并返回当前动态链接库文件的句柄。在通过GetProcAddress(hDll,“Add”)和GetProcAddress(hDll,“Sub”)获得导入到应用程序中动态链接库的函数Add()和Sub()的指针。函数调用完毕后使用Freelibrary(hDll)卸载动态链接库文件。需要注意的是,在编译应用程序之前,要吧动态链接库文件复制到应用程序所在的目录下。
三、实验内容
(一)实验内容
- 创建一个动态链接库。
- 静态加载动态链接库。
- 动态加载动态链接库。
(二)主要代码
- 动态链接库simple的代码
// simpledll.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
extern"C"_declspec(dllexport)int Add(int x,int y);
extern"C"_declspec(dllexport)int Sub(int x,int y);
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
int Add(int x,int y)
{
int z;
z=x+y;
return z;
}
int Sub(int x,int y)
{
int z;
z=x-y;
return z;
}
- 静态加载动态链接库,需先将simpledll.dll复制到calldll的debug目录下,将simpledll.lib复制到calldll.cpp目录下
// calldll.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "calldll.h"
extern "C" _declspec(dllimport) int Add(int x,int y);
extern "C" _declspec(dllimport) int Sub(int x,int y);
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/
// The one and only application object
CWinApp theApp;
using namespace std;
int main(int argc,char*argv[],char* envp[])
{
int nRetCode = 0;
int x=7;
int y=6;
int add=0;
int sub=0;
printf("Call Dll Now!\n");
add=Add(x,y);
sub=Sub(x,y);
printf("7+6=%d,7-6=%d\n",add,sub);
return nRetCode;
}
- 动态加载动态连接库,需先将simpledll.dll复制到calldlladdress的debug目录下
// calldlladdress.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "calldlladdress.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/
// The one and only application object
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int s;
int nRetCode = 0;
typedef int (*pAdd)(int x,int y);
typedef int (*pSub)(int x,int y);
HMODULE hDll;
pAdd add;
pSub sub;
hDll=LoadLibrary("simpledll.dll"); //加载动态链接库文件SimpleDll.dll
if(hDll==NULL)
{
printf("LoadLibrary Error.\n");
return nRetCode;
}
else printf("LoadLibrary Success.\n");
add=(pAdd)GetProcAddress(hDll,"Add"); //得到动态链接库中函数Add()的内部地址
s=add(6,2);
printf("6+2=%d\n",s);
sub=(pSub)GetProcAddress(hDll,"Sub"); //得到动态链接库中函数Sub()的内部地址
s=sub(6,2);
printf("6-2=%d\n",s);
FreeLibrary(hDll); //释放动态链接库SimpleDll.dll
return nRetCode;
}
四、实验结果和总结
实验结果
- 在建立simpledll后通过Microsoft Visual Studio提供的DumpBin.exe应用程序查看动态链接库的导入导出函数,结果如下:
- 建立静态调用calldll后,选择Project->Setting命令,在Link选项卡中的Option文件框中输入动态链接库的导入/导出库文件SimpleDll.lib,如下图
- 静态调用结果如下
- 动态调用结果如下
总结
- 该实验完成了动态链接库的建立和调用。函数Add()和Sub()在动态链接库文件simpledll.cpp中,分别完成两个整数的相加和相减。以及调用该动态链接库的静态加载calldll,动态加载calldlladdress.
- 使用dumpbin应用程序查看时,如果文件路径中文件夹名有空格会查看失败,静态加载连接库需将simlpe.dll复制到文件calldll的debug目录下,并且将simple.lib文件复制到calldll.cpp所在目录下,而动态加载只需将simlpe.dll复制到文件calldlladdress的debug目录下。