VS2019动态链接库
必须安装此项
VC动态链接库的分类:
Visual C++支持三种DLL,它们分别是Non-MFCDLL(非MFC动态库)、MFC Regular DLL(MFC规则DLL)、MFC Extension DLL(MFC扩展DLL)。
非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;
MFC规则DLL包含一个继承自CWinApp的类,但其无消息循环;可以被其他语言调用
MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。
动态链接库中的导出接口可以使用Dependencies工具进行查看
微软官方有提供depends,可以查看exe文件的依赖库,仅适用于winxp/win7/win8,但是不能用于win10,会卡死报错.
推荐Dependencies,可以应用在win7/win8/win10,不支持winxp
Dependencies使用
软件下载:
链接:https://pan.baidu.com/s/1OBoZfom1yRrJl4EI0WhaLA 提取码:6666
查看dll文件中的函数:
查看exe执行文件用的函数和dll文件:
非MFCDLL
创建项目
framework.h文件
该文件在编译Dll时需要将所有在你的项目中需要使用的头文件包含进去
关于dllmain.cpp
DLL程序入口点函数DllMain:
仅导出资源的DLL可以没有DllMain函数
参数1:HMODULE hModule 指向DLL本身的实例句柄
参数2:DWORD ul_reason_for_call 指明了DLL被调用的原因,可以有以下4个取值:
DLL_PROCESS_ATTACH:当DLL被进程 <<第一次>> 调用时,导致DllMain函数被调用,同时ul_reason_for_call的值为DLL_PROCESS_ATTACH,
如果同一个进程后来再次调用此DLL时,操作系统只会增加DLL的使用次数,不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。
DLL_PROCESS_DETACH:当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的ul_reason_for_call值是DLL_PROCESS_DETACH。
★如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。
这就意味着DLL在进程结束前没有机会执行任何清理工作。
DLL_THREAD_ATTACH:当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,
并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。 新创建的线程负责执行这次的DLL的DllMain函数,
只有当所有的DLL都处理完这一通知后,系统才允许线程开始执行它的线程函数。
DLL_THREAD_DETACH:如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),
系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。
注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数
参数3:保留,目前没什么意义
DLL有2种导出函数方式:
一种是导出函数,供被DLL调用的程序使用;
另外一种是供DLL内部调用使用,不提供导出
DLL导出函数的方式:*.def 文件或 __declspec(dllexport) 关键字
*.def文件函数导出方式:
1.添加def文件
2.文件中的第一个语句必须是 LIBRARY 语句。此语句将 .def 文件标识为属于 DLL。LIBRARY 语句的后面是 DLL 的名称。链接器将此名称放到 DLL 的导入库中
3.
EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。
注释语句,在语句前面加分号 “;”
4.创建函数实现cpp文件
注意:要加入预编译头文件
5.CTRL+B 编译
_declspec(dllexport) 函数导出方式:
1.为了规范创建头文件
#pragma once extern "C" _declspec(dllexport) int dec(int a, int b);//声明导出函数
2.函数的实现
3.编译 CTRL+B
实例工程下载:
链接:https://pan.baidu.com/s/1UueQ8mdVjxqZRLt2XlUVww
提取码:6666
函数的调用
隐式调用
隐式链接的特点是由编译器完成对DLL的加载和程序结束时对DLL的卸载工作,
如果程序结束时如果还有其他应用程序使用该DLL,那么系统会使DLL的使用计数减1,
当DLL的使用计数降为0时,会将DLL从内存中删除
优缺点:隐式链接DLL的方法简单实用,但缺少灵活性
使用方法:使用隐式链接DLL开发时,首先,需要将DLL的引入库文件(*.lib--编译生成DLL时,会一起生成的)与应用程序进行静态链接,
因为引入库文件包含DLL的各种输出资源,如导出函数,导出类等信息,这些信息指向DLL的函数指针等等,EXE执行时,
DLL被 “自动” 加载,EXE退出时DLL被 “自动” 卸载。
隐式链接也有两种方法:一种是通过代码,一种是通过属性设置
通过属性设置
通过代码
1.在h文件中加入代码
#pragma comment(lib,"D:\\bb\\yinsi\\Dll1.lib")
#pragma comment(lib,"Dll1.lib")
也可以使用默认目录:把dll拷贝到工程文件目录
【lib的链接有编译器完成,所以绝对路径,还是相对路径无所谓,程序运行时不需要】
2.对导入函数加声明
_declspec(dllimport) int add(int a, int b);
3.把dll文件拷贝到exe所在的目录
显式调用
显示链接方式是完全由编程者用API 加载和卸载DLL,编程者可以决定何时加载DLL,加载哪个DLL,何时卸载DLL,卸载哪个DLL等。
★优缺点:显示链接方式充分体现了DLL的灵活性,是比较常用的调用DLL方式。但是与静态链接相比稍微复杂了些
使用方法:
LoadLibrary(...):该 API 用于加载指定的DLL;
GetProcAddress(...):该 API 用于获取DLL中导出函数的指针, 即导出函数的入口点;
FreeLibrary(...):该 API 用于卸载指定的DLL
注:如果程序中多次调用LoadLibrary(...)加载同一DLL时,在卸载的时候也要调用相应次数的FreeLibrary(...)进行卸载
typedef int(*p)(int a, int b); //定义函数指针 void CyDlg::OnBnClickedButton1() { //HMODULE hmod=LoadLibrary(_T("D:\\bb\\y\\Debug\\Dll1.dll"));//加载动态链接库 HMODULE hmod = LoadLibrary(_T("Dll1.dll"));//加载动态链接库 /* 参数:LPCTSTR lpFileName 模块名 返回值:如果函数成功,则返回值是模块的句柄 如果函数失败,则返回值为NULL */ if (!hmod) { AfxMessageBox(_T("动态链接库加载失败")); ExitProcess(0); } p pf = (p)GetProcAddress(hmod,"add");//获取函数首地址 /* 参数1:HMODULE hModule DLL模块句柄 参数2:LPCSTR lpProcName 函数名, 注意:只能是ANSI 返回值:如果函数调用成功,返回值是DLL中的输出函数地址 如果函数调用失败,返回值是NULL */ if (!pf) { AfxMessageBox(_T("add函数首地址加载失败")); ExitProcess(0); } int c = pf(10,20);//函数的使用 BOOL b2=FreeLibrary(hmod);//释放动态链接库 /* 参数:HMODULE hLibModule DLL模块句柄 返回值:如果该函数成功,则返回值为非零值。 如果函数失败,则返回值为零 */ }
说明:
使用LoadLibrary显式链接,在这个函数的参数中可以指定DLL文件的完整路径。如果不指定路径,Windows将遵循如下的搜索顺序来定位DLL:
①EXE文件所在的目录:
②进程的当前工作目录:
③Windows系统目录:例如:C:\WINDOWS\system32
④Windows目录:例如:C:\WINDOWS
⑤环境变量的目录:我的电脑-->属性-->高级-->环境变量
●进程的当前工作目录:使用函数SetCurrentDirectory(...)设置的路径,或者从父进程继承而来的路径,使用GetCurrentDirectory(...)得到。
●EXE文件所在的目录:本EXE文件所在的绝对路径, 使用GetModuleFileName得到。
两者可能不同。
▲GetProcAddress函数可以有两种方式取得DLL导出函数的入口点:
例如:
GetProcAddress(hMod, "add"); //按照函数名称方式;
GetProcAddress(hMod, MAKEINTRESOURCEA(1)); //按照导出函数序号方式;
MFC规则DLL
1、MFC规则DLL特点:
●DLL内部可以使用MFC类库;
●可以被其他所有支持DLL技术的语言所调用。
2、MFC规则DLL的入口点函数:
默认情况下DLL的入口点函数都是DllMain,MFC规则DLL也不例外,但是因为是支持MFC的,所以在MFC规则DLL中,DllMain函数已经被MFC所封装,所以在你的工程中是看不到DllMain函数的,就好像在MFC对话框工程中你找不到WinMain函数一样的,不过也有一些补救的技巧,就是InitInstance()与ExitInstance()函数,当进程初始化调用DLL时,DLL中会默认调用那个InitInstance()函数,当EXE退出时DLL会调用ExitInstance()函数,但是当有新线程时,处于程序安全性考虑则没有什么好的办法进行处理。
“规则的”意味着它不同于MFC扩展DLL,在MFC规则DLL的内部虽然可以使用MFC,但是其与应用程序的接口不能是MFC。而MFC扩展DLL与应用程序的接口可以是MFC,可以从MFC扩展DLL中导出一个MFC类的派生类
3、MFC规则DLL分为两类:
●静态链接到MFC的规则DLL:与MFC库静态链接,会将MFC类库的代码直接编译生成到DLL文件中,在调用这种DLL的接口时,MFC使用DLL的资源,因此,不需要模块状态的切换,但是缺点就是使用这种方式生成的DLL文件大小比较大。
●动态链接到MFC的规则DLL:可以和调用DLL的EXE同时动态链接到MFC库,在这种情况下,MFC使用主应用程序(即:EXE程序)的资源句柄来加载资源模版,这样,当DLL和应用程序中存在相同ID的资源时,就要进行模块的切换,以便MFC能够找到正确的资源模版。
创建项目(动态链接到MFC的规则DLL)
重载函数(添加ExitInstance函数)
点击类视图-->选中工程APP
点击属性页-->点击重写
添加ExitInstance函数
添加对话框资源
右击工程-->添加-->资源
在CPP文件中写资源对话框显示的导出函数
调用MFC规则Dll
调用方法与非MFCDLL调用方法相同
MFC规则DLL的模块状态切换
void ShowDLLDlg() { AFX_MANAGE_STATE(AfxGetStaticModuleState());//模块状态切换--方法一 CDialog dlg(IDD_DIALOG1); dlg.DoModal(); }
注意:代码加在DLL导出函数中
void ShowDLLDlg() { //模块状态切换--方法二 HINSTANCE hSaveInst = AfxGetResourceHandle();//获取当前资源模块句柄 //说明:获取exe的实例句柄,为了后面恢复 AfxSetResourceHandle(theApp.m_hInstance);//设置程序目前要使用的资源模块句柄 /* 说明:把dll的实例句柄设置为当前程序要使用的实例句柄 参数:HINSTANCE hInstResource 模块句柄 */ CDialog dlg(IDD_DIALOG1); dlg.DoModal(); AfxSetResourceHandle(hSaveInst); //说明:把exe的实例句柄设置为当前程序要使用的实例句柄 }
注意:代码加在DLL导出函数中
void CMFCcallDlg::OnBnClickedButton2() { //模块状态切换-方法三 HINSTANCE hExeInst = GetModuleHandle(NULL); //获取本进程的实例句柄 HINSTANCE hDLLInst = GetModuleHandle(_T("MFCdll.dll")); //获取动态链接库的模块句柄 ASSERT(hExeInst && hDLLInst);//条件为真继续执行,条件为假中断执行 AfxSetResourceHandle(hDLLInst); //说明:把dll的实例句柄设置为当前程序要使用的实例句柄 ShowDLLDlg(); AfxSetResourceHandle(hExeInst); //说明:把exe的实例句柄设置为当前程序要使用的实例句柄 }
注意:代码加在EXE程序中(调用函数的程序中)
MFC规则dll教程视频:
https://www.bilibili.com/video/BV1yd4y1Z7SA/