1.动态链接库通常都不能直接运行,也不能接受消息,他们是一些独立的文件,其中包含能被可执行程序或其他DLL调用来完成某项工作的函数,只有在其他模块调用动态链接库时,它才能发挥作用,在实际编程时,我们可以把完成某种功能的函数放在一个动态链接库中,然后提供给其他程序调用
(1)静态库:函数和数据都被编译进一个二进制文件(通常扩展名为.lib),在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件,发布产品时,不需要发布静态库
(2)动态库:使用动态库时,往往提供两个文件:一个引入库(.lib)文件和一个DLL(.dll)文件,动态库和静态库有本质区别,对一个DLL来说,其引入库文件包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的数据和函数,使用动态库时,编译链接可执行文件时,只需要链接该DLL的引入库文件,该DLL中的函数代码和数据并不复制到可执行文件中,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL导出的函数,这时,发布产品时,要一同发布所需的动态链接库。
(3)使用动态链接库的好处
1.可以采用多种编程语言来编写
2.增强产品的功能
3.提供二次开发的平台
4.简化项目管理
5.可以节省磁盘空间和内存
6.有助于资源的共享
7.有助于实现应用程序的本地化(比如支持多语言)
(4)动态链接库的加载:显示加载和隐式加载
2.Win32 DLL的创建和使用
(1)Win32 Dynamic-Link Library
(2)Dumpbin命令:应用程序如果想要访问某个DLL中的函数,那么该函数必须是已经被导出的函数,可以利用Visual Studio提供的命令行工具:Dumpbin查看一个DLL中有哪些导出函数 dumpbin –exports dll1.dll
(3)从DLL中导出函数:为了让DLL导出一些函数,需要在每一个将要被导出的函数前面添加标识符:_declspec(dllexport)
(4)名字改编:不同的C++编译器会采用不同的规则进行名字改编
(5)隐式链接方式加载DLL
1. 将DLL库的.lib文件复制到要用此链接库的程序目录下,这个文件中包含了动态链接库中导出的函数的符号名
2. 在程序中,Project\Settings—link—Object/library modules 选项编辑框中输入对应的.lib
3. 调用链接库的函数之前,要对这些函数做一个声明:extern关键字
4. 然后就可以调用导出的函数了
(6)Depends工具:查看一个可执行模块依赖的动态链接库
(7)利用_declspec(dllimport)声明外部函数(代替extern),表明函数是从动态链接库中引入的,编译器可以生成运行效率更高的代码。如果调用的函数来自于动态链接库,应该采用这种方式声明外部函数
(8)通常在编写动态链接库时,都会提供一个头文件,在此文件中提供DLL导出函数原型的声明,以及函数的有关注释文档(包含此头文件就不用在代码中对库中函数做声明了)
(9)改造后的Dll1.h和Dll1.cpp
.h #ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport)
#endif
Dll1_API int add(int a,int b);
Dll1_API int sub(int a,int b);
.cpp #define Dll1_API _declspec(dllexport)
#include”Dll1.h”
int add(int a,int b)
{
return a+b;
}
int subint a,int b)
{
return a-b;
}
3.从DLL中导出C++类
(1)为了从动态链接库中导出一个类,需要在class关键字和类名之间加入导出标识符,这样就可以导出整个类了,但应注意,在访问该类的函数时,仍受限于函数自身的访问权限
例如:在上面的头文件中添加代码:
class DLL1_API Point
{
public:
void output(int x,int y);
}
这个类就可以被导出了
(2)Windows API提供的GetForegroundWindow函数将返回前景窗口的句柄,这个前景窗口就是当前用户正在使用的那个程序窗口
(3)如果采用隐式链接方式加载DLL,一旦DLL1.dll更新了,一定要将新的DLL1.dll和Dll1.lib文件复制到测试工程下,为了避免麻烦,可以把动态链接库文件所在的目录添加到系统的环境变量:path中,这样就可以自动搜索到这两个文件
(4)在实现动态链接库时,可以不导出整个类,而只导出该类中的某些函数
4.解决名字改编问题
(1)我们希望动态链接库文件在编译时,导出函数的名称不要发生改变,为了实现这一目的,在定义导出函数时,需要加上限定符:extern “C” 注意:“C“一定要大写
上面的.h和.cpp中改为:#define DLL1_API extern “C” _declspec(dllimport)
#define DLL1_API extern”C” _declspec(dllexport)
(2)利用限定符:extern”C”可以解决C++和C语言之间相互调用时函数命名的问题,但是这种方法有一个缺陷,就是不能用于导出一个类的成员函数,只能用于导出全局函数这种情况
(3)如果导出函数的调用约定发生了改变,那么即使使用了限定符extern”C”,该函数的名字仍会发生改编
(4)模块定义文件(DEF)的方式来解决名字改编问题
1.为工程Dll2添加一个模块定义文件,后缀名为.def,命名为Dll2.def
2.在Dll2.def文件中添加
LIBRARY Dll2
EXPORTS
add
sub
3. LIBRARY语句用来指定动态链接库的内部名称
EXPORTS语句的作用是表明DLL将要导出的函数,以及为这些导出函数指定的符号名(如果要导出的符号名和源文件中定义的函数名不同,要用 entryname = internalname)
5.显示加载方式加载DLL
(1)LoadLibrary函数,该函数的作用是将指定可执行模块映射到调用进程的地址空间
函数原型:HMODULE LoadLibrary(LPCTSTR lpFileName);
//该函数不仅能够加载DLL和可以加载可执行模块(.exe)一般来说,当加载可执行模块时,主要是为了访问该模块内的一些资源,该函数的参数指定了可执行模块的名称,如果调用成功,该函数返回所加载的哪个模块的句柄,该函数的返回值类型是HMODULE,该类型和HINSTANCE类型可以通用
(2)获取到动态链接库模块的句柄后,接下来就要想办法获取该动态链接库中导出函数的地址:GetProcAddress函数,该函数用来获取DLL导出函数的地址
函数原型:FARPROC GetProcAddress(HMODULE hModule,LPCSTR lpProcName);
// hModule指定动态链接库模块的句柄,即LoadLibary函数的返回值
// lpProcName指定DLL导出函数的名字或函数的序号(若是序号,则该序号必须在低位字中,高位子必须是0)
(3)隐式链接比较简单,但是会浪费系统内存资源
(4)显示链接示例:
HINSTANCE hInst;
hInst = LoadLibrary(“DLL2.dll”);
typedef int (*ADDPROC) (int a,int b);
ADDPROC Add = (ADDPROC)GetProcAddress(hInst,”add”);
If(!Add)
{
MessageBox(“ERROR”);
return;
}
else
{
Add(5,3);
}
(5)在访问DLL的客户端程序中,如果对DLL的访问已经完成,不再需要访问该DLL时,应该调用FreeLibrary函数释放该DLL
函数原型:BOOL FreeLibrary(HMODULE hModule);
6.调用约定:当DLL中导出函数采用的是标准调用约定时,访问该DLL的客户端程序也应该使用该约定类型来访问相应的导出函数
7.根据序号访问DLL中的导出函数
8.DllMain函数
(1)函数原型:BOOL WINAPI DllMain(HINSTANCE hinstDLL,
DWORD fdwReason,LPVOID lpvReserved);
// hinstDLL 动态链接库模块的句柄,当DLL初次被加载时,它的句柄会通过此参数传递进来
// fdwReason标记值,用来只是调用该DLL入口函数的原因
// lpvReserved保留参数
9.MFC DLL