【编程学习】windows C++ 动态链接库(DLL)的编写和使用

动态链接库,Dynamic Link Library,简称“DLL”,是一个预先经过编译的二进制程序,能够被其它程序所调用,但与可执行文件(.exe)不同,它不能独立运行,必须由其它的程序进行调用。


静态链接库与动态链接库

  • 静态链接库
    利用函数和数据编译成的一个二进制文件,扩展名为.Lib。当应用程序调用静态库进行编译链接时,链接器会从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe)。发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。
    ——特点:静态库对函数库的链接是放在编译时期完成的;程序在运行时与函数库再无瓜葛,移植方便;浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

  • 动态链接库
    在使用动态库的时候,往往提供两个文件:一个引入库文件(.lib)和一个DLL文件(.dll)。虽然引入库的后缀名也是“lib”,但是,动态库的引入库文件和静态库文件有着本质上的区别: 对一个DLL来说,其引入库文件包含该DLL导出的函数和变量的符号名,而DLL文件包含该DLL实际的函数和数据。在使用动态库的情况下,在编译链接可执行文件时,只需要链接该DLL的引入库文件,该DLL中的函数代码和数据并不复制到可执行文件中,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL中导出的函数。这时,在发布产品时,除了发布可执行文件以外,同时还要发布该程序将要调用的动态链接库。
    ——特点:动态库把对一些库函数的链接载入推迟到程序运行的时期;可以实现进程之间的资源共享(因此动态库也称为共享库);将一些程序升级变得简单。

Window与Linux执行文件格式不同,在创建动态链接库的时候有一些差异,本文编写时,主要利用VS2012在windows10系统上进行编译。


动态链接库的编写

  1. 创建Win32控制台程序, 选择“DLL”和“导出符号”选项。
    在这里插入图片描述
    得到:
    在这里插入图片描述
  2. 编写一个简单的相加函数
// MyDLL.h
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif

MYDLL_API float MyAdd(float a, float b);
// MyDLL.cpp 
#include "stdafx.h"
#include "MyDLL.h"

// Add函数
MYDLL_API float MyAdd(float a, float b)
{
	float c;
	c = a + b;
	return c;
}
  1. 编译
    Debug模式,win32平台,生成解决方案,在Debug文件夹中有“.lib”和“.dll”文件。

动态链接库的调用

动态链接库的调用共有两种方式:1)隐式调用;2) 显示调用。

1. 隐式调用

新建win32控制台程序”DLLtest”,将上一步中得到的MyDLL.h, MyDLL.lib, MyDLL.dll复制到新建项目源程序所在的文件夹中。此处可类比OpenCV的配置过程。

// DLLtest.cpp
#include "stdafx.h"
#include "MyDLL.h"
#pragma comment(lib, "MyDLL.lib")  //或者在依赖项中添加

#include <iostream>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	float a = 8.5;
	float b = 7.5;
	float sum;
	sum = MyAdd(a, b);
	cout<<"Test out:"<<sum<<endl;
getchar();
	return 0;
}

2. 显式调用

显示调用是指应用程序在运行过程中可以随时加载或卸载DLL,用法比隐式调用更加灵活,但调用方式相对较复杂:

#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;

typedef float(*Add)(float, float);

int _tmain(int argc, _TCHAR* argv[])
{
	float a = 4.5;
	float b = 5.5;
	float sum;
	Add add1;
	HINSTANCE hDLL;	
	hDLL = LoadLibrary("MyDLL.dll");   //加载动态链接库
	if(hDLL==NULL){
		FreeLibrary(hDLL);
		cout<<"Library is NULL!"<<endl;
	}
	add1 = (Add)GetProcAddress(hDLL, "MyAdd"); //获取函数"MyAdd"的地址
	if(add1==NULL){
		FreeLibrary(hDLL);
		cout<<"Pointer is NULL!"<<endl;
	}
	sum = add1(a, b);      
	cout<<"Test out:"<<sum<<endl;
	FreeLibrary(hDLL);  //卸载动态链接库

	getchar();
	return 0;
}

从上面的编程就可以看出来显示调用要复杂一些。用LoadLibrary()加载动态链接库, GetProcAddress() 获取导出函数的指针,用FreeLibrary() 卸载动态链接库,并且中间要用typedef定义跟要调用的函数同类型的指针函数[2][3]。

hdll=LoadLibrary(“DLL地址”);这里字符串类型是LPSTR,当是unicode字符集的时候会不行,因此要在项目属性-常规里面把默认字符集“使用unicode字符集”改成“使用多字节字符集”即可。


注意事项

1. 关键字 extern “C”
MYDLL_API float MyAdd(float a, float b); 换成 extern “C” MYDLL_API float MyAdd(float a, float b);, 这样可以使C语言等其它语言进行调用, 并选择“属性—常规—在静态库中使用MFC”去掉了对MSVCR110D.DLL的依赖[4]。

2.注意DLL编译的平台
如果是Win32,那么调用这个DLL应用程序也应该是Win32的平台,如果是x64,调用程序则就是在x64的平台下,不然会出现错误:fatal error LNK1120: 1 个无法解析的外部命令
有时也要注意DLL是在哪种模式下生成的。Debug模式生成,最好也在Debug下调用,对于Release也一样,不然会出现莫名的错误。

3. DLL可调用的函数返回值
不要传递非基本类型,例如C++中的string, 会报错[5]; 也不要返回局部变量值,例如char*, 在调用函数结束后,其指针将会被释放,从而无法获得返回值,最好可以当做函数的输入参数。


参考资料

[1] GCC编译过程与动态链接库和静态链接库
[2] dll文件生成使用: 隐式和显示的介绍和调用
[3] C++ dll调用-动态(显式): 有显示调用的操作过程和注意事项
[4] VS2010编写动态链接库DLL及单元测试用例,调用DLL测试正确性: Depends的使用,extern “C”
[5] c++ dll接口返回字符串

猜你喜欢

转载自blog.csdn.net/yideqianfenzhiyi/article/details/92798757