写在前面
本文示例基于上章的Dll1项目生成的动态链接库学习简单使用.
所需文件:因为上节的示例没有添加Dll1.h头文件,因此这里只需Dll1.dll,Dll1.lib
在本文中会添加Dll1.h头文件以优化动态链接库的创建.
既然要在项目中使用别人创建生成的dll, 那么首先得将dll加载到自己得项目中去才行.
这里有两种方式加载DLL到项目中:
① 隐式链接方式加载DLL
② 显式动态方式加载DLL
隐式链接方式加载DLL
大致步骤如下:
步骤如下:
(1) 在调用DLL的导出函数程序,需先声明即将要调用的外部函数, 使用extern关键字或标识符_declspec(dllimport)。注意区分_declspec(dllexport).
**_declspec(dllexport)**存在于动态链接库项目中的函数声明中,表明该函数是一个DLL导出函数.
**_declspec(dllimport)**存在于使用dll的项目中,表明该函数从一个dll中导入到本项目中.
一般一个dll会有一个对应的.h头文件来声明是导出还是导入函数(通过宏来区别,后续会在Dll1项目中完善),因为我们当前的Dll1项目没有.h头文件,因此需在使用Dll1.dll 导出的函数前声明该函数是导入函数.
例我们在同一个解决方案下新建Dll_test项目,并将该项目设置为启动项,然后隐式加载DLL:
(2) 然后配置该项目属性-》连接器-》输入-》依赖附加项-》编辑添加lib/Dll1.lib
注意这里的输入相对路径是.vcxproj文件所在目录,所以要加lib/
拷贝.lib 和 .dll 文件到该程序目录下,一般会在项目根目录下新建一个lib文件夹用来存放lib文件,dll文件一般放在存放exe的Debug目录下,这样发布软件的时候会把dll随exe一起发布(正好dll是必需的).
(3) 同样的也需将Dll1.dll拷贝到程序输出目录下。这里是输出exe文件的Debug目录:
编译运行DLL_test项目:
这样就是一个隐式加载dll, 并使用得示例了.
关于上面的第**(2)步,这里还有一种代替方案,就是通过#pragma comment** 来在代码中添加lib引入库文件来代替项目属性中得配置:
注意:这里需去掉项目属性-》链接器 –》附加依赖项中的lib/Dll1.lib
上面使用的是绝对路径,也可以使用相对路径,相对当前文件去找Dll1.lib,例当前想在main.cpp文件中导入lib,我们右键main.cpp打开文件所在路径:
可以看到lib目录和当前文件main.cpp在同一级目录下,所以我们可以直接这样:
再次编译运行, 结果同上:
显式动态方式加载DLL
不再 在链接阶段加载dll, 而是通过Windows库提供得API在代码中需要的时候动态的加载dll, 随即使用加载的dll中的导出函数, 使用完后释放对dll的引用.
动态加载的方式不再需要 lib导入库文件 和 .h头 文件,只需dll即可,通过下面三个函数加载、释放dll引用,获取dll中的导出函数.
LoadLibrary函数
函数原型如下:
HMODULE LoadLibrary( LPCTSTR lpFileName);
作用是指定的可执行模块映射到调用进程的地址空间.
LoadLibrary函数不仅能够加载DLL(.dll) ,还可以加载可执行模块(.exe). 一般来说,当加载可执行模块时,主要是为了访问该模块内的一些资源,例对话框资源、位图资源或图标资源等.
参数类型为LPCTSTR, 指向可执行模块的名称,既可以是一个.dll文件,也可以是一个.exe文件.
如果调用成功返回所加载的模块的句柄, 失败返回NULL, 因为是WinApi, 调用失败可以 GetLastError函数获取失败信息.
LoadLibrary是WinApi,因此需包含 windows.h
_T宏定义在tcha.h中,因此也需包含 tchar.h
当获取到动态链接库模块的句柄后,接下来就要想办法获取该动态链接库中导出函数的地址,可以通过GetProcAddress函数得到.
GetProcAddress函数
函数原型如下:
FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
参数hModule : 指定动态链接库模块的句柄,即LoadLibrary函数的返回值.
参数lpProcName :一个指向常量的字符指针,指定DLL导出函数的名字或名字的序号.
**注意:**如果该参数指定的是导出函数的序号,那么该序号必须在低位字中,高位字必须是0. 不过这里也会有相应的宏帮助我们构造, 后有介绍.
调用成功返回指定导出函数的地址,否则返回NULL.
编译运行发现这样获取函数指针失败:
这里查找原因, 找到Dll1.dll文件,在该目录下shift + 鼠标右键打开命令行窗口,使用dumpbin工具查看Dll1.dll内容:
可以看到已经发生名称改编了,而我们获取函数指针的GetProcAddress函数的第二个参数为“add”,Dll1.dll中没有add,只有?add@@YAHHH@Z,所以才返回NULL.
这里我们copy命令行窗口中的?add@@YAHHH@Z 和 ?subtract@@YAHHH@Z 代替add和subtract:
再次编译运行, 可以看到成功获取:
然后和此前一样使用即可:
动态链接中的问题
无论隐式还是显式加载,Dll1.dll都有发生名字改编,为什么隐式加载时可以直接使用函数名(add、subtract)调用呢?
以add函数为例,这里的解释是:
隐式加载时,通过函数名add调用时,因为是同一个编译器同一个调用约定,因此调用时编译器会自动将add 解释成改编后的名字: ?add@@YAHHH@Z,所以能正常调用.
而通过GetProcAddress函数获取函数指针是传递的是字符串参数”add”, 这里相当于去Dll1.dll的name项中查找是否有名字完全一样的add 接口,这里已经发生名字改编,所以肯定是找不到的,所以调用失败.
通过序号动态加载
也可以通过序号获取函数指针,同样通过dumpbin工具可以得知Dll1.dll中导出函数的序号和改编后的名称:
上面提到,GetProcAddress函数的lpProcName参数指定的是导出函数的序号,那么该序号必须在低位字中,高位字必须是0.
这里可以借助MAKEINTRESOURCE宏,MAKEINTRESOURCE宏会把指定的函数序号转换为相应的函数名字字符串,即将int类型的序号转换成LPCSTR类型的变量:
FreeLibrary函数
使用完接口后记得使用FreeLibrary释放对Dll1.dll的引用,因为内存空间中只会加载一份Dll1.dll,供其他进程使用,当某一进程使用完后会释放对Dll1.dll的引用.
同其他内核对象一样,当操作系统捕获到Dll1.dll的引用次数为0时,即没有任何进程使用Dll1.dll时,就会卸载Dll1.dll,释放内存.
函数原型如下:
BOOL FreeLibrary(HMODULE hModule);
当不再需要访问动态加载的DLL时,使用该函数释放对DLL的引用.
if ( !FreeLibrary(hDll1) )
{
//失败
}
与隐式加载DLL比较
对于同一类型的不同使用方式,这里免不了比较。目的不是去评价孰优孰劣,只是扩展了解下各自应用的场景,具体如何使用,取决于个人.
在动态加载DLL时,客户端程序不需要再包含导出函数声明的头文件(.h)和引入库文件(.lib),只需要.dll文件即可.
隐式链接实现比较简单,在编写客户端代码时就可以把链接工作做好,在程序中可以随时调用DLL导出的函数.
而动态显示加载的话,可以在需要的时候才加载DLL.
**应用场合:**在程序运行过程中只是在某个条件满足时才需要访问某个DLL中的某个函数时,可以考虑使用动态显示加载的方式访问DLL.
例:
假设某个程序需要访问十多个DLL,都采用隐式链接方式的话,那么在该程序启动时这些DLL都需要被加载到内存中,并映射到调用进程的地址空间,这将加大程序的启动时间.
而且一般来说,在程序运行过程中只是在某个条件满足时才需要访问某个DLL中的某个函数,在其他情况下都不需要访问这些DLL中的导出函数的话,将其加载到内存中资源浪费是比较严重的.
实际上, 采用隐式加载方式加载动态链接库时, 在程序启动时也是通过LoadLibrary函数加载该程序所需要的动态链接库的.文章来源:https://www.toymoban.com/news/detail-439235.html
代码
最后附上本文涉及代码:文章来源地址https://www.toymoban.com/news/detail-439235.html
//Dll1.cpp
int DLL1_API add(int a, int b)
{
return a + b;
}
int DLL1_API subtract(int a, int b)
{
return a - b;
}
//main.cpp
#include <iostream>
using namespace std;
//#pragma comment(lib, "D:\\vs2010_application\\动态库\\Dll1\\DLL_test\\lib\\Dll1.lib");
//#pragma comment(lib, "lib/Dll1.lib");
//
//extern int _declspec(dllimport) add(int a, int b);
//extern int _declspec(dllimport) subtract(int a, int b);
//extern int _declspec(dllimport) add(int a, int b);
//extern int _declspec(dllimport) subtract(int a, int b);
#include <windows.h>
#include <tchar.h>
int main()
{
/*cout << "累加函数测试: " << add(5, 3) << endl;
cout << "减法函数测试: " << subtract(5, 3) << endl;*/
HMODULE hDll1 = LoadLibrary(_T("D:\\vs2010_application\\动态库\\Dll1\\Debug\\Dll1.dll"));
if (hDll1 == NULL)
{
cout << "动态加载Dll1.dll失败!\n";
return -1;
}
定义一个add函数指针类型PADDPROC
//typedef int (*PADDPROC)(int a, int b);
PADDPROC pAdd = (PADDPROC)GetProcAddress(hDll1, "add");
//PADDPROC pAdd = (PADDPROC)GetProcAddress(hDll1, "?add@@YAHHH@Z");
//if (pAdd == NULL)
//{
// cout << "获取add函数指针失败!\n";
//}
//else
//{
// cout << "成功获取add函数指针!\n";
//}
同理定义一个subtract函数指针类型PSUBPROC
//typedef int (*PSUBPROC)(int a, int b);
PSUBPROC pSub = (PSUBPROC)GetProcAddress(hDll1, "subtract");
//PSUBPROC pSub = (PSUBPROC)GetProcAddress(hDll1, "?subtract@@YAHHH@Z");
//if (pSub == NULL)
//{
// cout << "获取subtract函数指针失败!\n";
//}
//else
//{
// cout << "成功获取subtract函数指针!\n";
//}
//因为subtract函数返回类型以及参数列表均和add函数相同, 因此也可以用PADDPROC接收
//PADDPROC pSub2 = (PADDPROC)GetProcAddress(hDll1, "subtract");
//PADDPROC pSub2 = (PADDPROC)GetProcAddress(hDll1, "?subtract@@YAHHH@Z");
//if (pSub2 == NULL)
//{
// cout << "获取subtract函数指针2失败!\n";
//}
//else
//{
// cout << "成功获取subtract函数指针2!\n";
//}
//通过序号获取函数指针
typedef int (*PADDPROC)(int a, int b);
PADDPROC pAdd = (PADDPROC)GetProcAddress(hDll1, MAKEINTRESOURCE(1));
if (pAdd == NULL)
{
cout << "获取add函数指针失败!\n";
}
else
{
cout << "成功获取add函数指针!\n";
}
typedef int (*PSUBPROC)(int a, int b);
PSUBPROC pSub = (PSUBPROC)GetProcAddress(hDll1, MAKEINTRESOURCE(2));
if (pSub == NULL)
{
cout << "获取subtract函数指针失败!\n";
}
else
{
cout << "成功获取subtract函数指针!\n";
}
cout << "累加函数测试: " << pAdd(5, 3) << endl;
cout << "减法函数测试: " << pSub(5, 3) << endl;
if ( !FreeLibrary(hDll1) )
{
cout << "卸载Dll1.dll失败!\n";
}
getchar(); //system("pause");
return 0;
}
到了这里,关于动态链接库(三)--动态链接库的使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!