动态连接库.docx

上传人:b****1 文档编号:158153 上传时间:2023-04-28 格式:DOCX 页数:19 大小:872.46KB
下载 相关 举报
动态连接库.docx_第1页
第1页 / 共19页
动态连接库.docx_第2页
第2页 / 共19页
动态连接库.docx_第3页
第3页 / 共19页
动态连接库.docx_第4页
第4页 / 共19页
动态连接库.docx_第5页
第5页 / 共19页
动态连接库.docx_第6页
第6页 / 共19页
动态连接库.docx_第7页
第7页 / 共19页
动态连接库.docx_第8页
第8页 / 共19页
动态连接库.docx_第9页
第9页 / 共19页
动态连接库.docx_第10页
第10页 / 共19页
动态连接库.docx_第11页
第11页 / 共19页
动态连接库.docx_第12页
第12页 / 共19页
动态连接库.docx_第13页
第13页 / 共19页
动态连接库.docx_第14页
第14页 / 共19页
动态连接库.docx_第15页
第15页 / 共19页
动态连接库.docx_第16页
第16页 / 共19页
动态连接库.docx_第17页
第17页 / 共19页
动态连接库.docx_第18页
第18页 / 共19页
动态连接库.docx_第19页
第19页 / 共19页
亲,该文档总共19页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

动态连接库.docx

《动态连接库.docx》由会员分享,可在线阅读,更多相关《动态连接库.docx(19页珍藏版)》请在冰点文库上搜索。

动态连接库.docx

动态连接库

动态链接库(DLL)

一、相关概念

动态链接库(DynamicLinkLibrary):

动态链接库通常都不能直接运行,也不能接收消息。

它们是一些独立的文件,文件后缀一般为.DLL,其中包含供其他可执行程序或其它DLL调用的函数。

只有在其它模块调用动态链接库中的函数时,它才发挥作用。

例如,WindowsAPI中的所有函数都包含在DLL中。

其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。

他们一般位于C:

\windows\System32或类似的目录下。

通俗一点说,动态链接库就是将很多函数放到一起形成一个集合模块,注册后供其他应用程序运行时动态调用。

这许许多多的函数又可分为内部函数和导出函数。

内部函数是用来在动态链接库内部调用的函数,主要用来实现动态链接库的实际功能;导出函数,顾名思义就是供外部模块或者应用程序在运行的时候调用的,是应用程序和动态链接库间的接口。

导出函数包含在导出表中,导出表包含动态链接库中所有可以被外部调用的函数名(对外的接口)。

顾名思义,动态链接库,是动态链接,它是相对于静态链接而言的。

静态库:

函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。

在其他程序使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据,并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.EXE文件)。

发布产品时,只需发布.EXE文件即可,不需要发布.LIB文件。

优点:

无须包括函数库所包含的函数代码,应用程序可以利用标准的函数集;

缺点:

两个应用程序运行时同时使用同一静态链接库中的函数,需要使用同一函数代码的两份拷贝,降低内存使用率。

动态库:

在其他程序使用动态库的时候,往往要用到DLL提供者提供的两个文件:

一个引入库(.LIB)和一个DLL(.DLL)。

但这里的.LIB文件和静态库中的.LIB文件有着本质的差别,这里的.LIB只包含导出的函数、变量的符号名,.DLL包含实际的函数和数据。

在编译链接时,只需要链接.LIB,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,才去加载DLL,并访问DLL中的导出函数。

优点:

可以采用多种编程语言来编写;提供二次开发的平台;简化项目管理;可以节省磁盘空间和内存,等。

动态链接库DLL和可执行文件EXE的区别:

可执行文件运行起来后有自己的独立的进程空间,而动态链接库的导出函数被动态链接到应用程序的进程空间,这样多个应用程序可以共享一份代码副本。

另外,动态链接库可以包括一个导出表,记录该动态链接库对外提供的函数接口。

二、DLL的开发、声明和调用

1、Win32DLL和MFCDLL的编写。

见下面的例子。

2、DLL中声明导出函数的三种方式:

使用_declspec(dllexport)、使用extern、使用.def文件

3、导出整个类或仅导出类中的部分成员函数。

导出整个类:

在类定义的关键字class后面加_declspec(dllexport)

导出成员函数:

在成员函数定义的最前面加_declspec(dllexport)

4、调用动态链接库的两种方式:

隐式链接:

需在工程设置中添加对.lib的引用或者使用#pragmacomment(lib,"Dll1.lib")

显式加载:

需要调用LoadLibrary()或者类似的函数加载动态链接库,再使用GetProcessAddress()获得要调用的每个函数的函数指针,使用完毕后,调用FreeLibrary()卸载DLL。

这两种加载方式都需要预先声明外部函数的类型,然后才能调用成功。

只是声明的方式有所不同。

三、实例

1:

创建一个Win32DLL工程Dll1

1.1使用_declspec(dllexport)声明导出函数

在Dll1.cpp中输入以下代码:

#include

#include

intAbsSub(inta,intb)//注意,前面没有加_declspec(dllexport)导出声明

{

return(a>b?

a-b:

b-a);

}

_declspec(dllexport)intAdd(inta,intb)//需要导出的函数名前面要加导出声明

{

returna+b;

}

_declspec(dllexport)intSub(inta,intb)

{

returna-b;

}

_declspec(dllexport)intSub(inta,intb,boolbAbs)

{

if(bAbs)

returnAbsSub(a,b);

else

returnSub(a,b);

}

class/*__declspec(dllexport)*/CMyPoint

{

public:

voidPrint(intx,inty)

{

HWNDhWnd=:

:

GetForegroundWindow();

HDChdc=:

:

GetDC(hWnd);

charbuf[20];

memset(buf,0,20);

sprintf(buf,"x=%d,y=%d",x,y);

:

:

TextOut(hdc,10,10,buf,strlen(buf));

:

:

ReleaseDC(hWnd,hdc);

};

voidShow(intx,inty)

{

HWNDhWnd=:

:

GetForegroundWindow();

HDChdc=:

:

GetDC(hWnd);

charbuf[20];

memset(buf,0,20);

sprintf(buf,"x=%d,y=%d",x,y);

:

:

TextOut(hdc,10,30,buf,strlen(buf));//仅仅y坐标不同。

:

:

ReleaseDC(hWnd,hdc);

};

};

classCPerson

{

public:

/*__declspec(dllexport)*/voidShowName(constchar*name)

{

HWNDhWnd=:

:

GetForegroundWindow();

HDChdc=:

:

GetDC(hWnd);

:

:

TextOut(hdc,10,50,name,strlen(name));

:

:

ReleaseDC(hWnd,hdc);

};

};

编译链接后在debug目录下可以找到dll1.lib和dll1.dll两个文件。

注意,如果代码中每个函数名前都没有声明_declspec(dllexport),编译时将不会生产dll1.lib文件。

这样的DLL将没有使用价值。

 

这里有一个问题,调用这个.DLL的其他程序如何知道其中的函数名称和用法呢?

答案是:

在DOS下使用DLL导出函数查看命令dumpbin/exportsdll1.dll,就可以看到dll1.dll文件包含了上述2个Add和Sub的信息。

Dumpbin.exe文件一般位于C:

\ProgramFiles\MicrosoftVisualStudio\VC98\Bin目录下。

如果在任意目录不能执行Dumpbin,则需要先运行C:

\ProgramFiles\MicrosoftVisualStudio\VC98\Bin\VCVARS32.BAT,使系统将该路径设置为缺省路径。

Dumpbin查看结果如下:

ordinalhintRVAname

1000001005?

Add@@YAHHH@Z

210000100A?

Sub@@YAHHH@Z

3200001014?

Sub@@YAHHH_N@Z

其中RVA列包含的数值表示导出函数在DLL模块中的偏移地址,通过该地址可以在模块中找到该函数。

Name列是导出函数的名称,但我们发现函数名字被改编了。

但其中没有AbsSub的信息,这是因为我们没有在AbsSub函数名前加入导出声明_declspec(dllexport)。

因此,在要导出的函数名前面一定要加上导出声明。

1.2使用extern“C”_declspec(dllexport)解决函数名改编问题

加上extern“C”之后,用C++写的DLL就可以在纯C环境使用了,但写DLL时就不能使用类了,因为C语言不支持类。

因此,除非特殊需求,一般不使用这种方式。

使用dumpbin命令查看,发现函数名字没有被改编。

1.3使用.def文件导出函数声明解决函数名改编问题(见后面的MFCDLL实例)

.def是由一个或者多个用于描述DLL属性的文本文件。

它包含以下一些模块定义语句:

LIBRARY:

指出动态链接库的名字,链接器负责将该名字放到动态链接库中;

DESCRIPTION:

描述该动态链接库的用途;

EXPORTS:

指出被导出函数的名称和序号;

例如:

LIBRARYDllA

DESCRIPTION“实现数据库的操作”

EXPORTS

Add@1

Delete@2

Member@3

对于使用MFC应用程序向导(AppWizard)生成的动态链接库,应用程序向导(AppWizard)会自动生成一个.def文件;对于非MFCDLL,可手工添加该文件到工程中。

使用dumpbin命令查看,发现函数名字没有被改编。

2、以隐式加载的方式使用DLL

2.1以_declspec(dllimport)方式声明外部函数

创建一个MFC对话框工程DllTest来调用上述DLL中的3个函数。

加入3个按钮,并添加代码。

如下:

_declspec(dllimport)intAdd(inta,intb);//使用_declspec(dllimport)声明DLL函数

_declspec(dllimport)intSub(inta,intb);//注意:

是dllimport,不是dllexport

_declspec(dllimport)intSub(inta,intb,boolbIsAbs);

voidCDllTestDlg:

:

OnBtnAdd()

{

CStringstr;

str.Format("3+4=%d",Add(3,4));

MessageBox(str);

}

voidCDllTestDlg:

:

OnBtnSub()

{

CStringstr;

str.Format("3-4=%d",Sub(3,4));

MessageBox(str);

}

voidCDllTestDlg:

:

OnBtnAbssub()

{

CStringstr;

str.Format("abs(3-4)=%d",Sub(3,4,true));

MessageBox(str);

}

编译发现,出现LNK2001链接错误。

将dll1.lib复制到当前工程目录下,并做如下工程设置:

再次编译将会通过。

但运行却又出现错误,提示找不到dll1.dll文件。

将dll1.dll也复制到当前工程目录下,再次运行后,一切就正常了。

从上面的编译链接和运行的过程可以知道:

dll1.lib文件是链接器进行链接时才需要的,它只是提供了dll1.dll文件中导出函数的函数名等信息,并没有包含实际的函数和数据。

函数的实际功能是在程序运行时动态的调用dll1.dll文件时才加载的。

也就是说,DLL的调用者在开发时需要.lib文件,发行时需要.dll文件。

●也可以不用在工程中进行dll1.lib的设置,在需要调用DLL函数的代码的前面,添加如下语句:

#pragmacomment(lib,"Dll1.lib")

这和上图中在工程设置里写上Dll1.lib的效果一样。

●使用ViaualStudio提供Depends工具可以查看一个EXE文件或者DLL文件在运行时所依赖的所有动态链接库。

如下图所示,说明dlltest.exe文件需要dll1.dll,mfc42d.dll等的支持。

 

2.2以extern方式声明外部函数

上面是使用_declspec(dllimport)对外部函数进行的声明,它明确告诉编译器,函数是来自于DLL的,也是较好的声明方式。

还有一种extern声明外部函数的方式,将上述dll1.cpp文件的声明中的_declspec(dllimport)替换成extern即可,运行效果也是一样的。

如下:

externintAdd(inta,intb);//使用extern声明DLL中的函数

externintSub(inta,intb);

externintSub(inta,intb,boolbIsAbs);

2.3使用头文件完善函数的声明,明确告知使用者函数的调用方式。

上面讲过,可以使用DOS命令dumpbin来查看DLL的导出函数有哪些,但dumpbin命令看不到函数的具体参数,返回类型等具体的函数原型。

因此,作为DLL的开发者,有必要提供完整的导出函数的原型,明确告知调用者DLL的用法。

为Dll1工程添加Dll1.h头文件,代码如下:

#ifndefDLL1_API

#defineDLL1_API_declspec(dllimport)

#endif

//intAbsSub(inta,intb)//实际工作中删掉这一行而不是注释掉,隐藏设计创意

DLL1_APIintAdd(inta,intb);

DLL1_APIintSub(inta,intb);

DLL1_APIintSub(inta,intb,boolbAbs);

在Dll1.cpp文件的最前面加上对DLL1_API的定义,如下:

//在此对DLL1_API进行声明,表示当前是导出模式。

//而本DLL的最终使用者不需进行任何声明,默认使用导入模式。

#defineDLL1_API_declspec(dllexport)

将Dll1.h文件复制到DllTest工程的目录下,包含进工程的.cpp文件中去,并注释掉原有的_declspec(dllexport)声明。

这样,同一个头文件就可以被开发者和使用者共同使用了。

3、以显式加载的方式使用DLL

这种方式需要调用LoadLibrary()或者类似的函数,来加载动态链接库,再使用GetProcessAddress()获得要调用的每个函数的函数指针,使用完毕后,调用FreeLibrary()卸载DLL。

这种方式不需要*.lib文件,也不需要包含*.h文件。

但需要知道被调用函数的原型,以便为GetProcessAddress()的返回值定义相应的函数指针。

3.1建立一个MFCDLL工程MyDll

3.2在MyDll.cpp文件中

添加函数声明

#include"MyDll.h"

#ifdef_DEBUG

#definenewDEBUG_NEW

#undefTHIS_FILE

staticcharTHIS_FILE[]=__FILE__;

#endif

voidMyFun1(CWnd*h);

voidMyFun2(CWnd*h);

添加函数实现:

CMyDllApptheApp;

voidMyFun1(CWnd*h)

{

CBrushnewBrush;

newBrush.CreateSolidBrush(RGB(0,0,255));

CBrush*pOldBrush;

CClientDCdc(h);

pOldBrush=dc.SelectObject(&newBrush);

dc.Ellipse(50,30,300,200);

dc.SelectObject(pOldBrush);

}

voidMyFun2(CWnd*h)

{

CPennewPen;

CPen*pOldPen;

newPen.CreatePen(PS_SOLID,1,RGB(0,0,255));

CClientDCdc(h);

CRectrect(40,20,310,210);

InvalidateRect((HWND__*)h,&rect,TRUE);

pOldPen=dc.SelectObject(&newPen);

dc.Ellipse(50,30,300,200);

dc.SelectObject(pOldPen);

}

3.3.def文件的编写:

;MyDll.def:

DeclaresthemoduleparametersfortheDLL.

LIBRARY"MyDll"

DESCRIPTION'MyDllWindowsDynamicLinkLibrary'

EXPORTS

;Explicitexportscangohere

MyFun1

MyFun2

注意:

.def文件注释是使用分号;而不是//

3.4编译生成MyDll.Dll文件

将生成的MyDll.dll文件拷贝到D:

\WINDOWS\system32目录下.这一部叫做动态链接库文件的注册.注意:

Debug和Release编译方式生成的该文件位置不一样。

怎么找?

3.5使用动态链接库

1建立使用动态链接库的工程(顺便介绍MFCDLL的使用)

其他步骤默认既可。

涉及如下对话框界面

添加代码:

1.声明全局变量

在TesDllDlg.cpp中添加如下代码:

HINSTANCEhDLL=NULL;//声明全局变量hDLL用于存放DLL的句柄并初始化为空

typedefvoid(*MYFUN1)(CWnd*h);//声明函数指针类型,用它来声明变量MyFun1;

MYFUN1MyFun1;

typedefvoid(*MYFUN2)(CWnd*h);

MYFUN2MyFun2;

如图:

2添加按钮点击消息响应函数

voidCTestDllDlg:

:

OnLoadDll()

{

//TODO:

Addyourcontrolnotificationhandlercodehere

if(hDLL!

=NULL)

{

MessageBox("你已经装载了MyDll.DLL文件!

");

return;

}

hDLL=LoadLibrary("MyDll.DLL");

if(hDLL==NULL)

{

MessageBox("无法装载MyDll.DLL文件!

");

return;

}

MyFun1=(MYFUN1)GetProcAddress(hDLL,"MyFun1");

MyFun2=(MYFUN2)GetProcAddress(hDLL,"MyFun2");

}

voidCTestDllDlg:

:

OnRunDll()

{

//TODO:

Addyourcontrolnotificationhandlercodehere

if(hDLL==NULL)

{

MessageBox("你还没有装载MyDll.DLL文件!

");

return;

}

for(inti=0;i<=10;i++)

{

MyFun1(this);

Sleep(1000);

MyFun2(this);

Sleep(1000);

}

}

程序运行结果:

反复绘制实心的椭圆和空心的椭圆,如下图:

需要特别注意的是,如果函数名字被改编了,在使用GetProcessAddress()时,其第二个参数需要使用改编后的函数名。

另外,使用DEF导出函数时,不允许进行函数重载,但使用_declspec(dllexport)导出时就可以,其原因是_declspec(dllexport)对函数进行了名称改编。

另外也可根据导出函数的序号来获取函数指针。

但不推荐使用这种方法。

如:

MYFUN2Draw2=(MYFUN2)GetProcAddress(hDLL,MAKEINTRESOURCE

(2));

MYFUN2Draw2=(MYFUN2)GetProcAddress(hDLL,“MyFun2”);

是一样的效果。

四、DllMain

可有可无。

如有,其中应尽量少的写代码。

可以在此做一些初始化的工作。

BOOLWINAPIDllMain(

HINSTANCEhinstDLL,//handletoDLLmodule

DWORDfdwReason,//reasonforcallingfunction

LPVOIDlpvReserved//reserved

);

作业:

1、提交:

编写一个Win32DLL工程MaxMin,对外提供带2个参数和3个参数的求最大值和最小值的4个导出函数:

intMax(inta,intb,intc);intMax(inta,intb);intMin(inta,intb,intc);intMin(inta,intb);

并另外编写一个MFC对话框工程TestMaxMin,使用隐式链接方式对MaxMin.dll进行测试。

2、思考:

MFCDLL中能导出某个类中的所有函数吗?

如果能,有哪些限制?

为什么?

如何导出?

使用上述Dll1工程中的例子进行测试。

(考点:

DEF和declspec(dllexport)两种导出方式的比较及混合使用)

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2