动态链接库

动态链接库

 

(推荐采用“显示链接”)

1.为了让所写的DLL被其它语言或者C++编译器编译出来的程序调用,可以采用模块定义文件(.def),让输出的函数符号名不发生改变;

2.访问DLL的客户端应用程序在调用DLL中的函数时,要采用和DLL导出时一样的调用约定.

 

一 DLL初步:

 

1.是Windows操作系统的基础

2.它由完成某项工作的函数组成,本身不能直接运行,也不能接收消息,但能被可执行程序或DLL调用.

3.Windows API中的所有函数都包含在DLL中.Kernel32.dll,User32.dll,GDI32.dll最重要.

Kernel32.dll包含用于管理内存,进程和线程的函数;

User32.dll包含用于执行用户界面任务的函数;

GDI32.DLL包含画图和显示文本的函数.

 

二 静态库和动态库

 

1.静态库

函数和数据被编译到.lib的二进制文件.如果使用了静态库,在编译链接时,链接器把静态库中

需要的函数和数据复制链接到应用程序中,建立可执行文件.在发布时,只需要发布生成的可执行

文件就行了,不需要发布静态库.

 

2.动态库

1)动态库包含引入库(.lib)和DLL;

2)引入库包含被DLL导出的函数和数据的符号名;

3)DLL包含实际的函数和数据;

4)在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据不复制到可执行文件中,

在运行时,再去加载DLL,访问DLL中导出的函数.

 

三 使用DLL好处

 

1.应用程序可以使用多种编程语言来编写

  如,业务逻辑可以用VC++或者Delphi创建DLL,由VB编写界面并调用DLL;

2.增强产品的功能

  发布实现产品的DLL规范,让其它开发商以此开发出适用于他们的DLL来替代我们的DLL,

让产品调用新的DLL,从而实现产品的增强,例如界面的更换等;

3.提供二次开发平台

  在销售产品的同时,为用户提供DLL作为二次开发平台,让用户调用DLL中提供的函数功能来编写

适用于自己业务产品,从而实现二次开发;

4.简化项目管理

  对大型项目,可以进行项目细分,采用DLL实现不现的功能,从而加快项目进度;

5.节省磁盘空间和内存

  对不同项目,如果要访问相同功能,则可以以DLL形式只提供一份,以节省磁盘空间.

如果多个应用程序使用同一个DLL,这个DLL页面只要放入内存一次,所有程序都可以共享页面.

6.共享资源

  可以编写一个纯资源的DLL,给其它应用程序去访问.

7.实现应用程序的本地化

  如果应用程序支持多语言,对每种语言,可以编写一个只支持本地语言的DLL.

 

动态链接库被多个进程访问

  同一个DLL可以被多个应用程序访问.

  应用程序进程启动---系统分配虚拟地址空间---系统分析可执行模块---系统从输入信息中搜索应用程序要调用的DLL

  ---系统加载要调用的DLL---系统为DLL分配虚拟内存---映射DLL的代码页面到进程的地址空间

  ---第二个进程启动---系统分析虚拟地址空间---第二个进程也要访问相同DLL---只需要将DLL页面映射到第二个进程地址空间.

 

 

四 DLL加载的两种方式

1.隐式链接 

  缺点:1.在程序启动时,一次性加载DLL中你要用到的所有函数到内存,映射到进程的地址空间,这样会加大进程启动时间.

       2.加载到内存里面的DLL中的功能函数,有可能不是每一个都要被程序调用,这样,资源得不到合理的控制,容易出现浪费资源的现象.

1)建立DLL例子:

 

编译后生成:

Dll1.lib    //引入库,包含导出的函数和数据符号

Dll1.dll    //动态库,实际的函数和数据代码

Dll1.exp    //导出库

 

可以使用VS提供的dumpbin查看Dll1.lib,Dll1.dll导出了哪些函数

dumpbin -exports Dll1.dll

 

 1    0 00011082 ?add@@YAHHH@Z = @ILT+125(?add@@YAHHH@Z)

 2    1 0001106E ?subtract@@YAHHH@Z = @ILT+105(?subtract@@YAHHH@Z

 

 ?add@@YAHHH@Z

 ?subtract@@YAHHH@Z

 就是经过编译器改编的函数名int add(int a,int b)与int subtract(int a,int b)(C++编译器会改变函数名称---名字改编)

 

2)调用编写的DLL

 

 

注意:

1.必须将先前编译后的Dll1.lib和Dll1.dll拷贝到要调用的工程目录下;

2.在工程---XX工程属性---配置属性---链接器---输入---将模块添加到程序集

---添加Dll1.lib(你要调用的引入库文件名)

 

可以使用

dumpbin -imports DllTest.exe看可执行程序调用了哪些DLL

也可以使用VS提供的Depends来打开你要查看的文件的依赖关系.

 

3)查看文件的依赖性

命令:dumpbin

工具:Depends

 

4)利用头文件,提供调用接口

 

在编写DLL时,提供头文件,并在头文件中包含原型申明以及相关的注释,方便客户调用需要的功能函数

 

 

在调用DLL的代码中#include头文件就行了.

 

5)修改Dll1.h,使头文件不仅方便客户调用,而且还可以为DLL本身使用

 

在头文件中用_declspec(dllimport),如果客户在自己的源文件中没有定义,则导入,此时头文件为客户使用;

#define DLL1_API _declspec(dllimport)

在DLL源文件中用_declspec(dllexport),以申明导出函数,此时头文件为DLL源文件使用.

#define DLL1_API _declspec(dllexport)

 

 

 

 

 

6)在DLL中导出C++类

 

宏放在类名前,则会导出类中的所有函数

 

 

 

 

 

 

 

 

7)导出类中的某个函数

 

只需要改变宏在类中的位置,放到需要导出的函数前就行了.

 

 

 

 

 

 

 

//调用DLL的代码源文件不变

 

8)关于名字改编

  C++编译器在导出函数时,会对函数的名字进行名字改编,并且每一个C++编译器对"名字改编"有不同的方案,

有可能会产生不同的名字.如果用C语言编写的客户端或者其它C++编译器编译出来的客户端程序调用C++编译出来的DLL中的某个函数,就会出现不能调用的情况.

  因此,在程序开发中,我们需要的就是经过C++编译器编译的DLL中,导出的函数名字不发生改变.这样,可以

给C语言编写出来的客户端进行调用.

可以用以下代码进行解决: 

注意:

1.下面的 "C" 为大写;

2.在头文件中和源文件中都必有用 extern "C",不能只有一边用.

#define DLL1_API extern "C" _declspec(dllexport)

#define DLL1_API extern "C" _declspec(dllimport)

 

 

9)采用extern "C"的局限性

1.extern "C" 只能解决C和C++相互调用;

2.extern "C" 只能导出全局函数,不能导出一个类中的成员函数;

3.如果导出的函数时,调用约定发生改变,即使使用了 extern "C" 也会发生名字改编.

 

_stdcall调用约定即是标准调用约定,即是WINAPI,也是Pascal调用约定;

什么都不加,默认的为C调用约定.

 

 

10)通过模块定义文件.def来解决名字改编的问题

 

在DLL工程中新增加模块定义文件.def

 

 

EXPORTS 关键字

EXPORTS 关键字可以在第一个定义所在的同一行或前一行上。.def 文件可以包含一个或多个 EXPORTS 语句。

导出 definitions 的语法为:

 

entryname[=internalname] [@ordinal [NONAME]] [PRIVATE] [DATA]

 

entryname 是要导出的函数名或变量名。这是必选项。如果导出的名称与 DLL 中的名称不同,则通过 internalname 指定 DLL 中导出的名称。例如,如果 DLL 导出函数 func1(),要将它用作 func2(),则应指定:

 

EXPORTS

func2=func1

 

 

2.显示链接  

  优点:只在需要的时候加载DLL,并调用其中的函数.

  LoadLibrary()

  GetProcAddress()

  FreeLibrary()

 

 1)

 

 LoadLibrary()动态加载DLL和.exe以调用其中的资源

HINSTANCE LoadLibrary( //映射一个可执行模块到一个调用进程的地址空间

  LPCTSTR lpLibFileName

); 

 

 GetProcAddress()获得DLL中导出函数的地址

FARPROC GetProcAddress( //获取指定导出的DLL中函数地址

  HMODULE hModule, 

  LPCWSTR lpProcName    //可以使用宏MAKEINTERSOURCE()把一个整形转换为一个指向常量字符的指针,来调用函数所在的序号

);

 

FreeLibrary()减少加载的DLL的引用计数,计数变为零时,这个模块将从调用进程的地址空间被,这个句柄不再有效

BOOL FreeLibrary( 

  HMODULE hLibModule

); 

 

 

 

 

 

注意:1.因为是动态加载,所以只把.dll文件拷贝到应用程序目录里,不再需要.lib文件了.

     2.如果调用约定发生变化,那么在DLL中和应用程序调用代码中都要使用相同的约定(例如:_stdcall).

     3.如果DLL发生名字改编(不使用.def文件),在客户应用程序动态加载时会出现找不到地址的问题,因为DLL在编译时,已经发生名字改编.

 

3.DllMain()函数

  设计DLL时的入口函数,非必要.

  在使用中,用DllMain()做简单的调用,别做太复杂的调用.

 

BOOL WINAPI DllMain(

  HANDLE hinstDLL,   //DLL模块句柄

  DWORD dwReason,    //指示DLL在哪种情况下被调用

  LPVOID lpvReserved

);

 

  1.当DLL被初次加载时,这个DLL模块的句柄会通过 hinstDLL 传递进来,如果DLL中的函数需要得到当前DLL模块名称,并利用它作为参数来调用其它函数,就可以使用DllMain().

  2.只要有DLL中有DllMain(),就一定会加载它.

 

4.支持MFC类库的调用

1)带静态链接MFC规则DLL----只发布DLL;

2)使用共享MFC规则DLL----发布DLL,并且客户操作系统中还要有要使用的MFC的DLL

3)MFC扩展类----导出MFC的类

 

 

小结:

  1.为了让所写的DLL被其它语言或者C++编译器编译出来的程序调用,可以采用模块定义文件(.def),让输出的函数符号名不发生改变;

  2.访问DLL的客户端应用程序在调用DLL中的函数时,要采用和DLL导出时一样的调用约定.

 

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值