动态链接库的建立与调用

一:实验目的

(1)理解动态链接库的实现原理。

(2)掌握Windows系统动态链接库的建立方法。

(3)掌握Windows环境下动态链接库的调用方法。

二:实验准备知识:动态链接库介绍

​ 动态链接库(Dynamic Link Library DLL)是一个可执行模块,它包含的函数可以由Windows应用程序调用以提供所需功能,为应用程序提供服务。

1.动态链接库基础知识

大型的应用程序都是由多个模块组成的,这些模块彼此协作,已完成整个软件系统的工作。其中可能有些模块的功能是通用的,被多个软件系统使用。在设计软件系统时,如果将所有模块的源代码都静态编译到整个应用程序的.exe文件中,会产生两个问题,一是应用程序过大,运行时消耗较大的内存空间,造成系统资源的浪费;二是在修改程序时,每次程序的调整都必须编译所有的源代码,增加了编译过程的复杂度,也不利于阶段性的模块测试。

Windows系统提供了非常有效的编译和运行环境,可以将独立的模块编译成较小的动态链接库文件,并可对这些动态链接库单独进行编译和测试。运行时,只有在主程序需要时才将动态链接库装入内存并运行。这样不仅减少了应用程序的大小及对内存的大量需求,而且使得动态链接库可以被多个应用程序使用,从而充分利用了资源。Windows系统中的一些主要系统功能都是以动态链接库的形式出现的,如设备驱动器等。

动态链接库文件在Windows系统中的扩展名为.dll,它由全局数据结构、若干函数组成,运行时被系统加载到进程的虚拟地址空间中,成为调用进程的一部分。如果与其他的动态链接库没有冲突,该文件通常映射到进程虚拟地址空间地址上。

2.动态链接库入口函数

DllMain()函数是动态链接库的入口函数,当Windows系统加载动态链接库时调用该函数,DllMain()函数不仅在将动态链接库加载到进程地址空间时被调用,在动态链接库进程分离时也被调用。

每个动态链接库必须有一个入口点,像用C语言编写其他应用程序时必须有一WinMain()函数一样,在Windows系统的动态链接库中,DllMain()是默认的入口函数。函数原型如下:

BOOL APIENTRY DllMain(HANDLE hModule,

​ DWORD ul_reason_for_call,

​ LPVOID lpReserved

​ )

​ {

​ return TRUE;

}

其中参数hModule为动态链接库的句柄,其值与动态链接库的地址相对应。参数ul_reason_for_call指明系统调用该函数的原因。lpReserved说明动态链接库是否需要动态加载或卸载,lpReserved为NULL表示需要动态加载或使用FreeLibrary()卸载,即运行时用到该动态库链接库时才将其装入内存,当进程不用该动态链接库时,可以使用FreeLibrary()将动态链接库卸载。lpReserved为非NULL表示静态加载,进程终止时才卸载,即进程装入内存时同时将其动态链接库装入,进程终止时动态链接库与进程同时被卸载。

使用入口函数还能使动态链接库在被调用时自动做一些初始化工作,如分配额外的内存或其他资源;在撤销时做一些清除工作,如回收占用的内存或其他资源。需要做初始化或清除工作时,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_PROCESS_DETACH

当动态链接库从进程的地址空间被卸载时,系统将调用该动态链接库的DllMain()函数,给它传递参数ul_reason_for_call的值DLL_PROCESS_DETACH。当处理DLL_PROCESS_DETACH时,动态链接库执行与进程相关的清除操作,如堆栈的撤销等。

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时,要根据具体情况进行。

3.动态链接库导入/导出函数

动态链接库文件中包含一个导出函数表,这些导出函数有他们的符号名和标识名被唯一地确认,导出函数表中还包含了动态链接库中函数的地址。当应用程序加载动态链接库时,通过导出函数表中各个函数的符号名和标识名找到该函数的地址。如果重新编译动态链接库文件,并不需要修改调用动态链接库的应用程序,除非改变了导出函数的符号名和其它参数。

在动态链接库源程序文件中声明导出函数的代码如下:

_declspec(dllexport) MyDllFunction(int x,int y);

其中关键字_declspec(dllexport)表示要导出其后的函数MyDllFunction()。如果一个动态链接库文件中的函数还需要调用其他动态链接库,此时,动态链接库文件除了导出函数外,还需要一个导入函数,声明导入函数的代码如下:

_declspec(dllimport) DllAdd(int x,int y);

其中关键字_declspec(dllimport) 表示要导入其后的函数DllAdd(),在生成动态链接库时,链接程序会自动生成一个与动态链接库相对应的导入/出库文件(.lib文件),下面的例子中建立一个名为SimpleDll的动态链接库工程文件,在SimpleDll工程的Debug目录下,可以看到 SimpleDll.dll和 SimpleDll.lib 两个文件,其中SimpleDll.dll是编译生成的动态链接库可执行文件,SimpleDll.lib 就是导入/导出库文件,该文件中包含SimpleDll.dll文件名和SimpleDll.dll中的函数名Add()和 Sub(),SimpleDll.lib 是文件SimpleDll.dll的映像文件,在进行隐式链接时要用到它。

下面是一个动态链接库程序的例子。

#include ···

extern “C”_declspec(dllexport) int Add (int x,int y);

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;

}

应用程序要链接引用动态链接库的任何可执行模块,其.lib文件是必不可少的。除了创建.lib文件外,链接程序还要将一个输出符号表嵌入到产生的动态链接库文件。该输出符号表包含输出变量、函数和类的符号列表基函数的虚拟地址。

4.动态链接库的两种链接方式

当应用程序调用动态链接库时,需要将动态链接库文件映射到调用进程的地址空间中。映射方法有两种:一种是应用程序的源代码只引用动态链接库中包含的符号,当应用程序运行时,加载程序隐式地将动态链接库装入到进程的地址空间中,这种方法也称隐式链接;另一种方法是应用程序运行时使用LoadLibary()显式地加载所需要的动态链接库,并显式地链接需要的输出符号表。

当进程加载动态链接库是,Windows系统按以下搜索顺序查找并加载动态链接库。

应用程序的当前目录(将动态链接库文件复制到应用程序的.exe文件所在目录下)。

Windows目录下。

Windows\System32目录下。

PATH环境变量中设置的目录。

列入映射网络目录表中的目录。

(1)隐式链接

如果程序员建立了一个动态链接库文件(.dll),链接程序会自动生成一个与动态链接库相对应的导入/导出库文件(.lib文件)。该文件作为动态链接库的替代文件被编译到应用程序地工程项目中。当编译生成应用程序时,应用程序中的调用函数与.lib文件中导出符号名相匹配,若匹配成功,这些符号名进入生成的应用程序的可执行文件。.lib文件中也包含对应动态链接库文件名,但不含路径,它同样也被放入生成的应用程序的可执行文件。Windows系统根据这些信息发现并加载动态链接库,然后通过符号名实现对动态链接库函数的动态链接。

在调用动态链接库的应用程序中,声明要调用的动态链接库中的函数,需要写明extern”C”,它可以使其他编程语言访问所写的动态链接库中的函数。下面是通过隐式链接方法调用Simp[leDll.dll中的函数Add()和Sub()的方法,先建立一个控制台工程文件CallDll,在CallDll.cpp文件中输入代码:

extern ”C”_declspec(dllimport) int Add(int x,int y);

extern “C”_declspec(dllimport) int Sub(int x,int y);

int main(int argc,char* argv[])

{

​ 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 0;

}

为了能够使用调用程序CallDll.cpp正确地调用到动态链接库SimpDll.dll,在生成工程文件CallDll.cpp的执行文件之前,先将SimpleDll.dll复制到工程文件CallDll的Debug目录下,将SimpleDll.lib复制到CallDll.cpp所在目录下。然后在Microsoft Visual Studio的环境下的【Project Setting】对话框中的【Link】选项卡中输入动态链接库的导入/导出库文件SimpleDll.lib。

(2)显示连接

显示连接方式更适合于集成化的开发工具,如Visual Basic等,使用显示连接,程序员不必再使用导入/导出文件,而直接调用WIN32提供的LoadLibary()函数加载动态链接库文件,调用GetProcAddress()函数得到动态链接库中函数的内部地址,在应用程序退出之前,还应使用FreeLibrary()释放动态链接库。

下面是通过显示连接调用动态链接库的例子。

#include …

Int_tmain(int argc,TCHARargv[],TCHARenvp[])

{

int s;

int nRetCode=0;

typedef int (*pAdd)(int x,int y);

typedf int (*pSub)(int x,int y);

HMODULE hDll;

pAdd add;

pSub sub;

hDll=LoadLibrary(“SimpleDll.dll”);

add=(pAdd)GetProcAddress(hDll,”Add”);

s=add(6,2);

sub=(pSub)GetprocAddress(hDll,”Sub”);

s=sub(6,2);

FreeLibrary(hDll);

Return nRetCode;

}

在上面的例子中,使用类型定义关键字typedef定义了指向动态链接库中相同函数原型的指针,然后通过LoadLibrary(“SimpleDll.dll”)将到头了即可文件SimpleDll.dll加载到应用程序中,并返回当前动态链接库文件的句柄。在通过GetProcAddress(hDll,“Add”)和GetProcAddress(hDll,“Sub”)获得导入到应用程序中动态链接库的函数Add()和Sub()的指针。函数调用完毕后使用Freelibrary(hDll)卸载动态链接库文件。需要注意的是,在编译应用程序之前,要吧动态链接库文件复制到应用程序所在的目录下。使用显示连接方式,不需要使用相应的.dll文件

5.函数调用参数传递约定

动态链接库函数调用参数传递约定决定着函数参数传送时入栈和出栈的顺序,以及编译器用来识别函数名字的修饰约定。为了使不同的编译语言方便地共享动态链接库,函数输出时必须正确的调用约定。

Microsoft Visual C++6.0 支持的常用函数约定主要有_stdcall调用约定、C调用约定和_fastcall调用约定。

_stdcall调用约定相当于16位动态链接库库中经常使用的PASCAL调用约定。在Microsoft Visual C++6.0 中不再支持PASCAL调用约定,取而代之的是_stdcall。两者在实质上是一致的,即调用时函数的参数自右向左通过内存栈传递,被调用函数返回时清理传递参数的内存栈。

C调用约定(用_cdecl关键字说明)与_stdcall调用约定在参数传递顺序上是一致的,不同的是用于传递参数的内存栈是由调用者来维护的,使用便餐的函数只能使用该调用约定。

_fastcall调用约定的主要特点是快,因为它是通过寄存器来传递参数的,实际上它使用寄存器ECX和EDX传送前两个汉字,剩下的参数仍旧自右向左通过内存栈传递,被调用函数返回时清理传递参数的内存栈。因此,对于参数较少的函数使用关键字_fastcall可以提高其运行速度。

关键字_stdcall、_cdecl和_fastcall可以在函数说明时直接写在要输出的函数前面,也可以在Microsoft Visual C++6.0编译环境中进行设置。

三:实验内容

(1)在Windows环境下建立一个动态链接库。

(2)使用隐式调用法调用动态链接库。

(3)使用显式调用法调用动态链接库。

四:实验要求

​ 掌握动态链接库建立和调用方法。在Windows XP + Visual C++6.0 环境下建立一个动态链接库,并分别使用隐式和显式方式将其调用,从而体会使用动态链接库的优点。

五:实验结果

​ 可以看到通过建立动态链接库之后两个calldll01和calldll02分别可以运行,结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
六:源代码
dll:

#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;
}

calldll01:

#include "stdafx.h"
#include "calldll01.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// The one and only application object

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 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;
}

calldll02:

#include "stdafx.h"
#include "calldll02.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("dll.dll");
if(hDll=NULL)
{
  printf("LoadLibrary Error.\n");
return nRetCode;
}
else printf("LoadLibrary Success.\n");

add=(pAdd)GetProcAddress(hDll,"Add");
s=add(6,2);
printf("6+2=%d\n",s);

sub=(pSub)GetProcAddress(hDll,"Sub");
s=sub(6,2);
printf("6-2=%d\n",s);
FreeLibrary(hDll);
return nRetCode;
}

七:实验总结
本实验介绍了在Windows XP + Microsoft Visual C++6.0环境下建立于调用动态链接库的方法,使用动态链接库,除了可以节省内存空间、实现代码共享之外,还可以实现多种编程语言书写的程序相互调用,本次创建并使用动态链接库完成了calldll01与calldll02的实验,分别为静态加载和动态加载。

猜你喜欢

转载自blog.csdn.net/YuTinH/article/details/106327546