C++ 中的动态库和静态库(Windows)

2023-10-27,,

库:

在C/C++中,使用库(Library)的技术,可以将编译好的符号提供给第三方使用。

库有两种:

1、动态库 Dynamic-Link Library (DLL)    (Linux下叫做 Shared Library)

2、静态库 Static Library

一、动态库的创建和使用

创建DLL:

用VC创建一个类型为 “dll”的控制台项目,VC会自动创建DLL的项目框架

它自动生成一个DllMain函数,可以类比普通应用程序中的main函数

VC项目设置:

1、取消“预编译头文件”

2、改为 “/MTd编译”

3、修改输出的DLL的名字 (my.dll)

编译,得到 *.lib 和 *.dll,其中:

*.dll:

包含所有代码编译成的指令

*.lib:包含一个列表,表名my.dll中含有哪些符号,每个符号对应在dll中的位置。(被导出的符号)

所以,*.lib比*.dll的文件体积小得多

如果想导出一个全局函数,就用关键字 __declspec(dllexport)来声明

注意:这是VC平台特有的关键字,在linux平台下不可用

使用如下:

template <typename T>
__declspec(dllexport) void MySwap(T& obj1, T& obj2)
{
	T tmp = obj1;
	obj1 = obj2;
	obj2 = tml;
}

使用dll:

#pragma  comment(lib, "12_18_DLL01")

__declspec(dllimport) int Add(int a, int b);

int main()
{
	int ret = Add(1, 2);
	std::cout << ret << std::endl;
	return 0;
}

但这个地方我发现了一个问题:

__declspec(dllimport) int Add(int a, int b);

这行代码表示导入dll文件中的符号

但一开始我错误得写成了 export, 结果居然仍然是正确的

没有深究,初步推测是编译器优化了 (IDE: VS2015),甚至不写前面的关键字,直接写成函数的声明,程序也是能正确执行的

DLL的部署位置:

1、可执行文件所在目录

2、进程当前目录

3、系统目录 (C:\Windows\System32\    和  C:\Windows\System\)

4、Windows目录 (C:\Windows\)

5、环境变量 PATH 中的目录

通过初步接触DLL的发布和使用,相比普通的声明、定义,会发现DLL有以下作用:

1、隐藏了代码

2、公开了功能

当然,DLL的作用远不止如此

二、DLL的加载和卸载

DLL的加载:

DLL不能独立运行,只有当 *.exe 被运行时, *.dll 才会被加载运行。

在exe文件中有一些标识信息,表示该 exe 依赖哪些 dll 文件,操作系统据此去寻找、加载相应的dll文件。

exe 被加载到内存,形成一个进程 process,dll也被加载到内存,进程可以直接调用DLL中的函数(Code)

数据段和代码段:

在dll文件中,至少分为两个段:

1、代码段:

存储指令(函数体)

2、数据段:

数据段,存放全局变量

当 *.dll 被加载时,代码段只被加载一次,是公共的。

数据段被每个程序各自拷贝一份,是私有的

三、DLL中的动态内存管理

在dll中申请的动态内存,必须在dll中释放,否则会导致内存泄漏 (这是由Windows自己的特点决定的)

四、DLL中使用头文件

按照惯例,由模块的作者提供头文件,头文件中应该用类型、函数声明。

所以,我们在创建DLL时,应该附带一份头文件,而不是让使用者自己去写函数声明。

五、DLL中导出一个类

导出类的定义,其实就是导出其成员函数

类的声明格式:

class __declspec(dllimport) MyClass
{};

六、静态库的概念及使用

静态库:

static library,仅一个 *.lib 文件

静态库中直接就含有代码段和数据段,在链接过程中,是直接把里面的东西链接过来,形成完整的可执行程序

exe运行的时候不依赖 .lib 文件

创建:

1、建立一个新工程,工程类型为:静态库

2、不需要其他设置,直接编译链接生成,得到 *.lib文件

使用:

#pragma comment(lib, "MyLib.lib")

int MyAdd(int a, int b);    //通常发布会把把声明放到头文件中,一并发布

静态库的注意事项:

1、静态库的使用不太方便:

如果该静态库是VS2008编译的,那么APP也得用VS2008编译,版本必须一致

此外,运行时库(/MT、/MTd、/MD、/MDd)也必须要一致

因此可以看出,静态库的使用,约束条件是比较多的

静态库和动态库的对比:

静态库优点:

使用静态库,最后得到的可执行程序执行时对这个库不再依赖

动态库优点:

便于升级更新,只要保持接口不变,可以通过更新DLL来升级程序,而不需要重新编译程序

目前通常都使用动态库

但动态库也存在更多的安全方面的隐患,毕竟如果有人恶意替换DLL(能做到这一步的人往往是软件团队的内部人员),程序的执行将会违背编程者的本意.......

七、DLL的手动加载

之前加载DLL的方式是自动加载。

自动加载和手动加载:

1、自动加载

在编译时指定dll,则当exe程序启动运行时,首先加载相关的dll

(DLL在程序执行前被加载,在程序结束后被卸载)

2、手动加载

在编译时不指定dll,在运行时调用LoadLibrary来加载dll 

(这样做,可以自己决定什么时候加载、什么时候卸载DLL)

手动加载的方式:

#include <winsock2.h>
#include <windows.h>

使用 LoadLibrary 来加载dll,

使用 FreeLibrary 来卸载dll,

它提供了一种在运行时手动加载dll的技术手段,增加了编程的灵活性

(只要有*.dll就可以,也不需要 *.lib 和 *.h )

对DLL的要求:

1、要求待调用的函数按“C”方式编译(符号名即函数名)

extern "C" __declspec(dllexprot) int MyAdd(int a, int b);

2、dll文件放在可被系统搜索到的路径

代码:

#include <iostream>
#include <winsock2.h>
#include <windows.h>

int main()
{
	wchar_t* Path = L"12_18_DLL01.dll";
	HINSTANCE  handle = LoadLibrary(Path);	// 字符集兼容问题
	if (handle)
	{
		// 定义要找的函数原型
		typedef int(*DLL_FUNCTION_ADD) (int, int);
		// 找到目标函数的地址	注意实现必须用 extern "C" 的方式编译
		DLL_FUNCTION_ADD dll_func = (DLL_FUNCTION_ADD)GetProcAddress( handle, "Add");
		if (dll_func)
		{
			// 调用该函数
			int result = dll_func(1, 2);
			std::cout << result << std::endl;
		}
	}
	FreeLibrary(handle);
	return 0;
}

八、项目的静态编译

有一种场景,将以往VS生成的 *.exe 程序拷贝到其他 没有VS运行环境的机器上,这时候程序往往是无法运行的,这是因为,VC编译默认采用动态编译的方式,因此 要么给这个机器也安装一套运行环境,要么采用静态编译的方式生成可执行文件(*.exe)

静态编译后的程序,将会包含一切程序运行时所依赖的环境

对VC来说:

静态编译:

/MT  /MTd

动态编译:

/MD  /MDd

动态编译和静态编译的比较:

1、动态编译不方便发布,但生成的可执行文件体积较小

2、静态编译方便发布,但生成的可执行文件体积较大

《C++ 中的动态库和静态库(Windows).doc》

下载本文的Word格式文档,以方便收藏与打印。