动态链接库dll(Windows/C++)

1、概念

(1)动态链接库广泛用于Windows系统及应用程序,不能单独被执行,在应用程序运行期间被动态调用的模块文件。区别于静态链接库,均属于独立的代码编译模块,但静态链接库在调用方编译期间,被编译到程序里,与程序形成完成的运行(exe)文件。动态链接库只是将对外导出信息(声明)暴露给调用方,调用方在程序中仅依据暴露的信息进行使用,在编译期间,不会与动态链接库发生任何关系,只有在应用程序运行动态链接库所包含的功能时,动态库才被加载到进程空间,实现调用过程。
(2)静态链接库只需要发布调用的主执行程序即可,动态链接库需要将相关的dll文件全部打包发布。windowsAPI就包含在3个主要的动态库中,分别是Kernel32.dll、User32.dll、GDI32.dll

2、优势

可以将完成的某种功能放到动态库中,供给任何需要调用的程序,实现模块化,便于修改维护和后期的功能完善,实现与主程序降低耦合。同时由于dll为编译的二进制文件,可以突破编写语言限制,管理简化。此外,dll文件只在内存加载一次,供不同程序共享,实现资源共享化,减少内存资源浪费,比如dll负责图标、对话框模板等资源加载,则多个应用程序可共享这些资源。

3、创建和使用

在VS下使用VC++创建动态库分为如下几种:
(1)Win32创建的DLL
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
生成文件及dllmain函数如下,windows下指定调用约定是__stdcall,意味着这个函数以标准Pascal的方式进行调用,其中传入dllmain的hMoudle为dll自身的模块句柄,ul_reason_for_call为调用原因说明,为分支下四种情况,一般不需要修改dllmain函数,在清除或者加载dll时可在此处做相关处理
在这里插入图片描述
在当前工程下添加导出信息的声明头文件,将来此文件在隐式链接dll时需要用到,需要在文件内添加导出声明_declspec(dllexport),由于dll工程会预定义宏XX_EXPORTS(可在属性配置中C++预定义下查看添加),所以为了调用方便,可根据宏判断分别定义导入导出声明的宏,这样在调用方就只需要包含此头文件就可使用导出数据,而不需要单独添加导入声明。接下来添加导出类、数据、函数等,需要注意导出声明的位置,类的导出声明放在class后。
此外,全局变量在cpp文件定义(只在所有调用的cpp中定义一次),如果是头文件声明需要在cpp处加external,如果在头文件添加external声明,这样不需要在任何其他文件调用时添加external,而且避免全局变量重复定义后编译错误
在不同语言C/C++编译条件下,导出函数的名称可能会出现变化,C++实现了函数重载,编译期间会对函数名进行修饰,所以在dll的显示加载(后文)时需要用到函数名的时候会出现问题,可以通过命令dumpbin查看dll文件的导出函数名,C函数编译后函数名不会出现变化,所以需要在导出声明时添加extern "C"告诉编译器以C的方式编译,下图是dumpbin运行情况:
在这里插入图片描述
在这里插入图片描述
同时,导出函数在调用时,函数参数的入栈顺序,堆栈清理以及函数名修饰等需要有统一的调用约定,参能实现函数正确调用,windows下一般是_stdcall调用约定,所以在导出函数时还需要添加此声明
实例代码如下,利用导出函数、全局数据、类实现数字相加功能
在这里插入图片描述
导出声明代码:

#pragma once

#ifdef TESTPRODLL_EXPORTS
#define TESTPRODLL2_API __declspec(dllexport)
#else
#define TESTPRODLL2_API __declspec(dllimport)
#endif

//extern "C" 指定以C语言方式调用函数,C语言没有函数重载,所以能保证函数名不变
//
//WINAPI约定调用函数的规则,指参数入栈顺序为_stdcall

extern TESTPRODLL2_API int a;                      //导出变量

extern "C" TESTPRODLL2_API int WINAPI Add(int b);  //导出函数

class TESTPRODLL2_API ADD                          //导出类
{
public:
	 int WINAPI Do(int b);
};

extern "C" TESTPRODLL2_API int WINAPI GetA();           //获取全局变量

extern "C" TESTPRODLL2_API void WINAPI SetA(int b);     //设置全局变量

extern "C" TESTPRODLL2_API int WINAPI RunADDDo(int a);  //创建ADD对象,通过对外函数访问类

在cpp中实现如下:


#include "stdafx.h"
#include "TestAdd.h"

int a = 1;                   //全局变量定义需要加类型,而且必须定义

int Add(int b)
{
	int c = a + b;
	return c;
}

int ADD::Do(int b)
{
	return a + b;
}
int GetA()       //获取全局变量
{
	return a;
}

void SetA(int b) //设置全局变量
{
	a = b;
}

int RunADDDo(int a)  //创建ADD对象,调用方通过对外函数访问dll的类
{
	ADD add;
	return add.Do(a);
}

编译工程后,win32的dll就创建完毕,目录如下:
在这里插入图片描述
接下来调用该dll,实现dll中的功能,分为如下两种方式:
(a)隐式加载dll
需要dll的导出头文件XX.h和引入库文件XX.lib,在调用cpp中包含XX.h头文件,同时添加lib链接,可以在属性配置中添加,也可用#progma coment命令引入。
隐式加载dll通过lib将导出声明信息编译到执行程序中,在程序启动后会加载lib指定的dll,dll需要放在执行程序目录下,不论dll功能是否正在使用,都会被加载
配置链接lib:
在这里插入图片描述
在这里插入图片描述
命令引入lib:
#pragma comment(lib,“XX.lib”)
以上两种链接lib方式均可,以下为第二种方式实现

#include "stdafx.h"
#include "windows.h"
#include "TestAdd.h"
#pragma comment(lib,".\\x64\\Debug\\TestPro.DLL.lib") //注意表示当前目录下需要加‘.\’
int main()
{
	a = 2;
	int c = Add(3); //5
	ADD A;
	int d = A.Do(4);//6
	int m = RunADDDo(5); //7
	return 0;

(b)显式链接dll
显式链接dll只需要dll文件本身即可,前提是调用者需要清楚dll内部导出细节,比如函数名等信息,需要将dll复制到调用执行文件的目录下
显式链接dll的好处是程序在执行dll相关功能时动态加载dll文件,启动后不会立马加载dll文件,所以不会影响启动效果,也能够节省内存空间
加载方式如下:

#include "stdafx.h"
#include "windows.h"

int main()
{
	HINSTANCE hInstLibrary = ::LoadLibrary(L"TestPro.DLL.dll");  //加载文件
	if (hInstLibrary == NULL)
	{
		::FreeLibrary(hInstLibrary);
		return 0;
	}
	//
	//*******dll中导出全局数据访问**********//
	//
	//通过函数指针进行访问
	typedef int(__stdcall *FuncGetA)();
	FuncGetA testGetA = (FuncGetA)::GetProcAddress(hInstLibrary, "GetA");       //根据函数名进行访问
	int n = GetLastError();
	if (nullptr == testGetA || 0 != n)
	{
		::FreeLibrary(hInstLibrary);
		return n;
	}
	int a = testGetA();  //1

	typedef void(__stdcall *FuncSetA)(int);
	FuncSetA testSetA = (FuncSetA)::GetProcAddress(hInstLibrary, "SetA");       //根据函数名进行访问
	n = GetLastError();
	if (nullptr == testSetA || 0 != n)
	{
		::FreeLibrary(hInstLibrary);
		return n;
	}
	testSetA(4);       
	a = testGetA();      //4

	//
	//*******dll中导出函数访问**********//
	//
	typedef int(__stdcall *FuncAdd)(int);
	FuncAdd testAdd = (FuncAdd)::GetProcAddress(hInstLibrary, "Add");                   //根据函数名进行访问
	n = GetLastError();
	if (nullptr == testAdd || 0 != n)
	{
		::FreeLibrary(hInstLibrary);
		return n;
	}
	int sum = testAdd(5);   //9
	

	//
	//*******dll中导出类访问**********// 备注:此功能在dll添加导出函数中实现dll中类访问
	//
	typedef int( *FuncCreateObj)(int);
	FuncCreateObj testRun = (FuncCreateObj)::GetProcAddress(hInstLibrary, "RunADDDo");       //根据函数名进行访问
	n = GetLastError();
	if (nullptr == testRun || 0 != n)
	{
		::FreeLibrary(hInstLibrary);
		return n;
	}

	sum = testRun(1); //5

	::FreeLibrary(hInstLibrary);                                 //卸载文件

    return 0;
}

注意:显示加载要使用全局变量,需要在dll的头文件添加get/set函数进行操作,使用dll中的类需要添加对外导出函数进行间接调用
此外,对外声明可以通过另一种方式进行实现,需要使用def文件导出,def文件是模块定义文件,可以将需要导出的函数在此文件声明,编译后就将导出信息编译到lib文件,def文件使用如下
在这里插入图片描述
在这里插入图片描述
添加导出说明如下
在这里插入图片描述


;注释部分
LIBRARY TestPro.DLL.lib
EXPORTS 
GetA     @ 1
SetA     @ 2
Add      @ 3
RunADDDo @ 4

;导出函数名称,@序号为导出函数序号

同时修改导出头文件声明如下:

#pragma once


//extern "C" 指定以C语言方式调用函数,C语言没有函数重载,所以能保证函数名不变
//
//WINAPI约定调用函数的规则,指参数入栈顺序为_stdcall

extern  int a;                      //导出变量

extern "C"  int WINAPI Add(int b);  //导出函数

class ADD                          //导出类
{
public:
	 int WINAPI Do(int b);
};

extern "C"  int WINAPI GetA();           //获取全局变量

extern "C"  void WINAPI SetA(int b);     //设置全局变量

extern "C"  int WINAPI RunADDDo(int a);  //创建ADD对象,通过对外函数访问类

编译调用,可实现同样功能
以上代码均在VS2015下调试运行通过
(2)Win32创建支持MFC的DLL
创建如下:
山地车
在这里插入图片描述
如图,比win32下创建dll多了对外导出头文件和相关声明,同时主函数发生了变化,不是dllmian函数
而是main函数,增加了MFC支持,可以使用MFC相关类进行dll编程,dll实现包括调用等与win32基本一致,不多做描述
(3)MFC创建DLL
创建如下:
在这里插入图片描述
在这里插入图片描述
MFC使用了CWinApp类,添加了消息响应机制
在这里插入图片描述
在这里插入图片描述
以上是MFC创建dll时VS给出的所有内容,其他导出实现操作与win32基本一致,需要注意MFC下32位和64位的编译时不同,32位下会重定义错误,需要将WINAPI加到cpp中
以上是分享的dll相关的所有内容,欢迎转载,请注明出处!

  • 18
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值