动态链接库
DLL就是整个windows操作系统的基础。动态链接库不能直接运行,也不能接收消息。他们就是一些独立的文件。
Windows API中的所有函数都包含在DLL中。
其中三个最重要的DLL
- Kernel32.dll——它包含用于管理内存、进程和线程的各个函数:CreateThread
- User32.dll——它包含用于指定用户界面任务(如窗口的创建和消息的传送)的各个函数
- GDI32.dll——它包含用于画图和显示文本的各个函数
静态库和动态库
静态库: 函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE文件)。
动态库: 在使用动态库的时候,往往提供提供两个文件:一个引入库(静态库)(LIB)和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,而是在运行时候,再去加载DLL,访问DLL中导出的函数。
使用动态链接库的好处:
- 增强产品的功能(更换界面的DLL)
- 提供二次开发的平台(SDK基础版本)
- 简化项目管理(串行开发,以多个DLL的方式获取)
- 可以节省磁盘空间和内存
- 有助于资源的共享(对话框模块,字符串,图标)
- 有助于实现应用程序的本地化(多语言版本)
- 可以采用多种语言来编写
使用动态链接库
创建DLL
dumpbin命令
通过使用这个命令来知道库中导出的都是什么函数。
//提供给用户用的函数
//应用程序如果要访问某个DLL中的函数,那么函数必须是被导出的函数。
//为了让DLL导出一些函数,需要在每个将要被导出的函数前面加上_declspec(dllexport)
_declspec(dllexport)int add(int a, int b)
{
return a + b;
}
_declspec(dllexport)int substract(int a, int b)
{
return a - b;
}
名字改变===名字粉碎,区分不同的函数。
隐式链接
lib文件直接复制到当前文件路径下,对应的dll文件也要复制过去。
将静态库文件.lib添加到项目属性的链接器-输入-附加依赖项中。
extern int add(int a, int b);
extern int substract(int a, int b);
void CDLLMFCTestDlg::OnBnClickedButton1()
{
CString str;
str.Format(L"4+3 = %d",add(4,3));
MessageBox(str);
}
void CDLLMFCTestDlg::OnBnClickedButton2()
{
CString str;
str.Format(L"4-3 = %d",substract (4, 3));
MessageBox(str);
}
使用dumpbin -imports XXX.exe查看可执行文件导入了哪些DLL。
在.exe文件运行的时候,系统将为exe分配一个4GB的地址空间,然后加载模块会分析该应用程序的输入信息,从中找到该程序将要访问的动态链接库信息。然后在用户的机器上搜索这些动态链接库。
_ declspec(dllexport)与_declspec(dllimport)
与使用extern关键字这种方式对比,使用_declspec(dllimport)的标识符,它将告诉编译器是从动态链接库引入的。
extern表示函数是外部的全局函数。
_declspec(dllexport)是在类、函数以及数据的声明的时候使用。把DLL里面的相关代码暴露出来给其他应用程序使用。提供给别的应用程序使用。表示提供者。供DLL内部使用。
_declspec(dllimport)是在外部程序需要使用DLL内相关内容时使用的标识符。是把DLL中的相关代码插入到应用程序中去。表示使用者。不是DLL内部使用。
通常情况下,DLL的实现者和使用者不是同一个人,DLL+头文件方式。
具体:略......
头文件建议使用条件指令编译。是使得程序的可读性增强,灵活性增强。
//如果定义了DLL1_API,则什么也不干,如果没听已,则
#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport)
#endif
DLL1_API int add(int a, int b);
DLL1_API int substract(int a, int b);
从DLL中导出C++类
动态链接库导出整个类和仅导出该类的某些成员函数在实现方式的区别:如果在声明该类时,指定了导出标志,那么该类中所有的函数都被导出,否则只有那些声明时指定了导出标志的类成员才会被导出。
解决名字改编问题
编译器在生成DLL时,会对函数名进行改编。
我们可以使用如下代码防止名字改编: extern "C"
define DLL_API extern "c" _declspec(dllexport)
这样编译器就不会做改变,一个用C语言编写的客户端程序可以调用C++编写的DLL,其缺点就是不能导出一个类的成员函数,只能用于导出全局函数这种情况。
_stdcall标准的调用约定 C/C++ MFC
Delphi 用pacal 是从左至右的压栈方式。
.def文件
LIBRARY DLLNAME
EXPORTS //即使调用_stdcall约定,也不会发生改编,而智慧调用这里显示的
add //字符串
substract
EXPORTS语句引入了一个由一个多个definitions(导出的函数或数据)组成的节。每个定义必须在单独一行上。EXPORTS关键字可以在第一个定义所在的同一行上或在前一行上。.def文件可以包含一个或多个EXPORTS语句。
当DLL中导出函数采用的是标准调用约定时,访问该dll的客户端程序也应该采用该调用约定类型来访问相应的导出函数。
显式链接(动态方式加载DLL)
不需要lib文件。
LoadLibrary
注意名字是否被改编,调用的是哪个函数。
void CDLLMFCTestDlg::OnBnClickedButton1()
{
//动态加载DLL
HINSTANCE hInst = LoadLibrary(L"ZYXTDLL.dll");
//声明要加载的函数
typedef int (*ADDPROC)(int a, int b);
//从DLL获取函数地址,A通过导出函数的实际函数名
ADDPROC Add = (ADDPROC)GetProcAddress(hInst,"?add@@YAHHH@Z");
//或者
ADDPROC Add = (ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCEA(1));
if (!Add)
{
MessageBox(L"获取函数地址失败");
return;
}
CString str;
str.Format(L"4+3 = %d",Add(4,3));
MessageBox(str);
FreeLibrary(hInst);
}
因为调用LoadLibrary时动态加载动态链接库,所以不需要头文件和.lib文件。
如果我们在动态链接库中使用标准调用约定_stdcall,而在可执行程序中使用动态加载DLL,会发生名字重编,如果知道DLL中函数的序号,这时可以使用宏MAKEINTRESOURCE把序号转变成名字。
DLLMAIN函数
对可执行模块来说,入口函数是winmain。
对DLL文件来说,入口函数是DLLMAIN。
在编写DLL文件时,可以写DLLMAIN也可以不写。
函数原型
// 表示动态链接可以的模块句柄,当DLL初次被加载时,句柄可以通过这个参数传递进来。如果某些函数需要使用到当前DLL模块的句柄,那么就可以为该DLL提供DILLMAIN函数,然后通过参数,保存在一个全局变量中,以供其他函数使用。
BOOL WINAPI DllMain(
HINSTANCE hinstDLL,
DWORD fdwReason, // reason for calling function
LPVOID lpReserved ) // reserved
{
// Perform actions based on the reason for calling.
switch( fdwReason )
{
case DLL_PROCESS_ATTACH://当进程第一次加载DLL并调用DLLMAIN函数
// Initialize once for each new process.
// Return FALSE to fail DLL load.
break;
case DLL_THREAD_ATTACH://当前进程正在创建一个新线程
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH://线程结束
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH://进程结束
// Perform any necessary cleanup.
break;
}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}
评论区