动态链接库
(推荐采用“显示链接”)
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导出时一样的调用约定.
4230

被折叠的 条评论
为什么被折叠?



