动态链接库(三)--动态链接库的使用

2023-05-16

写在前面

本文示例基于上章的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:
1

(2) 然后配置该项目属性-》连接器-》输入-》依赖附加项-》编辑添加lib/Dll1.lib
注意这里的输入相对路径是.vcxproj文件所在目录,所以要加lib/
2

拷贝.lib 和 .dll 文件到该程序目录下,一般会在项目根目录下新建一个lib文件夹用来存放lib文件,dll文件一般放在存放exe的Debug目录下,这样发布软件的时候会把dll随exe一起发布(正好dll是必需的).
3
4
(3) 同样的也需将Dll1.dll拷贝到程序输出目录下。这里是输出exe文件的Debug目录:
5
编译运行DLL_test项目:
6

这样就是一个隐式加载dll, 并使用得示例了.

关于上面的第**(2)步,这里还有一种代替方案,就是通过#pragma comment** 来在代码中添加lib引入库文件来代替项目属性中得配置:
7
注意:这里需去掉项目属性-》链接器 –》附加依赖项中的lib/Dll1.lib

上面使用的是绝对路径,也可以使用相对路径,相对当前文件去找Dll1.lib,例当前想在main.cpp文件中导入lib,我们右键main.cpp打开文件所在路径:
8
9
可以看到lib目录和当前文件main.cpp在同一级目录下,所以我们可以直接这样:
10
再次编译运行, 结果同上:
11

显式动态方式加载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函数获取失败信息.

12
LoadLibrary是WinApi,因此需包含 windows.h
_T宏定义在tcha.h中,因此也需包含 tchar.h

当获取到动态链接库模块的句柄后,接下来就要想办法获取该动态链接库中导出函数的地址,可以通过GetProcAddress函数得到.

GetProcAddress函数

函数原型如下:

FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);

参数hModule : 指定动态链接库模块的句柄,即LoadLibrary函数的返回值.
参数lpProcName :一个指向常量的字符指针,指定DLL导出函数的名字或名字的序号.

**注意:**如果该参数指定的是导出函数的序号,那么该序号必须在低位字中,高位字必须是0. 不过这里也会有相应的宏帮助我们构造, 后有介绍.

调用成功返回指定导出函数的地址,否则返回NULL.

13

编译运行发现这样获取函数指针失败:
14

这里查找原因, 找到Dll1.dll文件,在该目录下shift + 鼠标右键打开命令行窗口,使用dumpbin工具查看Dll1.dll内容:
15
可以看到已经发生名称改编了,而我们获取函数指针的GetProcAddress函数的第二个参数为“add”,Dll1.dll中没有add,只有?add@@YAHHH@Z,所以才返回NULL.

这里我们copy命令行窗口中的?add@@YAHHH@Z 和 ?subtract@@YAHHH@Z 代替add和subtract:
16

再次编译运行, 可以看到成功获取:
17

然后和此前一样使用即可:
18
19

动态链接中的问题

无论隐式还是显式加载,Dll1.dll都有发生名字改编,为什么隐式加载时可以直接使用函数名(add、subtract)调用呢?

以add函数为例,这里的解释是:
隐式加载时,通过函数名add调用时,因为是同一个编译器同一个调用约定,因此调用时编译器会自动将add 解释成改编后的名字: ?add@@YAHHH@Z,所以能正常调用.

而通过GetProcAddress函数获取函数指针是传递的是字符串参数”add”, 这里相当于去Dll1.dll的name项中查找是否有名字完全一样的add 接口,这里已经发生名字改编,所以肯定是找不到的,所以调用失败.

通过序号动态加载

也可以通过序号获取函数指针,同样通过dumpbin工具可以得知Dll1.dll中导出函数的序号改编后的名称
20

上面提到,GetProcAddress函数的lpProcName参数指定的是导出函数的序号,那么该序号必须在低位字中,高位字必须是0.

这里可以借助MAKEINTRESOURCE宏,MAKEINTRESOURCE宏会把指定的函数序号转换为相应的函数名字字符串,即将int类型的序号转换成LPCSTR类型的变量:
21
22

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函数加载该程序所需要的动态链接库的.

代码

最后附上本文涉及代码:

	//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;
	}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

动态链接库(三)--动态链接库的使用 的相关文章

随机推荐