在C++开发中,库(Library)是代码复用的重要方式,库的存在形式本质上来说库是一种可执行代码的二进制,分为静态库(.lib、.a)和动态库(.dll、.so)
静态链接库
静态库是一种在编译时将代码直接嵌入到可执行文件中的库文件,它具有独立性、性能优势和版本控制等特点,适用于许多不同类型的项目和开发环境。如果你想要别人使用你的代码,但又不想将源代码公开的时候,也可以使用
优:静态写入封装,代码重用
缺:直接编译到exe(如若修改.lib,需重新编译exe)
创建静态链接库
这里以Visual Studio 2022为例
方法一:
也可以在项目属性页修改配置类型

若要自定义头文件,C/C++ → 预编译头 → 设置为"不使用预编译头"
在pch.h中添加函数或类预定义
1
2
3
4
5
6
7
|
#pragma once
void PPTe();
namespace test {
void castue();
}
|

在pch.cpp中写出完整的类与方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include <iostream>
#include "pch.h"
using namespace std;
void PPTe(){
cout << "Hello World " << endl;
}
namespace test {
void castue() {
cout << "Ualeo Teui" << endl;
}
}
|

选择“生成” -> “生成解决方案”(Ctrl+Shift+B)
在“x64” -> “Debug”中生成lib文件如下

将刚刚生成的lib文件和头文件打包复制到新项目文件夹

在源文件中预定义头文件,并且在代码中指定需要链接的库文件,添加代码为
1
2
|
#include "pch.h"
#pragma comment(lib, "TestLib.lib")
|
这样就成功导入静态链接库啦,可以使用静态库中的类与方法

方法二:
刚刚的指定需要链接的库文件代码不添加
将头文件和库文件打包在同一个文件夹,将该文件夹路径添加到项目属性 → VC++目录 → 包含目录和库目录,注意前面要加; (如果是方法一中直接复制文件到新项目就跳过此步)
项目属性中设置:打开项目属性 → 链接器 → 输入,在"附加依赖项"中添加 TestLib.lib

或者打开项目属性 → 链接器 → 命令行,在“其他选项”中添加 TestLib.lib

点击“应用” → “确定”,和上述功能相同

动态链接库
动态库指的是在程序运行过程中动态加载库的方式使用的库,也就是动态库的链接是发生在程序运行时期的,它和可执行文件是分开的,只是可执行文件在运行的某个时期调用了它。
优:程序自身的体积不会因为动态函数库变大
缺:就是程序运行过程中使用到了这些函数库内的功能时,若系统特定的位置没有对应的动态库,就会造成程序崩溃或者各种奇怪的问题
创建动态链接库
方法一:隐式调用
创建新项目模板为**“动态链接库”**
或配置属性 → 常规 → 配置类型:动态库(.dll)
函数声明与生成静态库不一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#pragma once
// DLL导出宏
#ifdef STRINGDYNAMICLIB_EXPORTS //若使用该语句,C/C++ → 预处理器 → 预处理器定义:添加STRINGDYNAMICLIB_EXPORTS
#define STRING_API __declspec(dllexport) //导出声明(__declspec(dllexport))告诉编译器哪些函数应该对外可见,没有导出的函数无法从外部调用
#else
#define STRING_API __declspec(dllimport)
#endif
namespace StringDynamic {
STRING_API void reverse(char* str, int length); // 反转字符串
STRING_API int countChar(const char* str, char c); // 统计字符出现次数
STRING_API char* concatenate(const char* str1, const char* str2); // 连接两个字符串
}
|

.cpp代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
#include "pch.h"
#include <cstring>
#include <cstdlib>
namespace StringDynamic {
STRING_API void reverse(char* str, int length) {
int start = 0;
int end = length - 1;
while (start < end) {
std::swap(str[start], str[end]);
start++;
end--;
}
}
STRING_API int countChar(const char* str, char c) {
int count = 0;
while (*str) {
if (*str == c) count++;
str++;
}
return count;
}
STRING_API char* concatenate(const char* str1, const char* str2) {
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
char* result = (char*)malloc(len1 + len2 + 1);
strcpy(result, str1);
strcat(result, str2);
return result;
}
}
|
选择“生成” -> “生成解决方案”(Ctrl+Shift+B),会生成dll文件和lib

同样创建一个新项目,将dll和lib打包复制到新项目文件夹中
1
2
3
|
#pragma comment(lib, "TestDll.lib")
extern "C" _declspec(dllimport) int __stdcall mySub(int x, int y); //导入函数时要跟声明类型的一样
|

方法二:显示调用
dll文件为方法一中生成的,将dll与exe放于同一目录
1
2
3
4
5
6
7
8
9
10
11
|
#include <windows.h> //使用LoadLibrary函数必须包含此头文件
#include <iostream>
using namespace std;
typedef int(__stdcall* lpSub)(int, int); //定义函数指针
int main() {
lpSub mySub; //声明函数指针变量 //动态加载dll到内存
HINSTANCE hModule = LoadLibrary(L"TestDll.dll"); //L -> 宽字符版本 -> 现代Windows兼容
mySub = (lpSub)GetProcAddress(hModule, "mySub"); //获取函数地址
cout << "2-1=" << mySub(2, 1) << endl; //若是32位程序,__stdcall类型则为_mySub@8
FreeLibrary(hModule); //释放dll
}
|

静态库与动态库对比

使用.def导出库
同样使用动态链接库项目,def导出可以隐藏函数名
pch.h
1
|
int myMul(int x, int y);
|
pch.cpp
1
2
3
|
int myMul(int x, int y) {
return x * y;
}
|
test.def
1
2
3
|
EXPORTS
Mul @13 NONAME //13为该函数序号 NONAME表示没有名字,即隐藏函数名
|

配置属性 → 链接器 → 输入中模块定义文件添加刚刚的test.def

选择“生成” -> “生成解决方案”(Ctrl+Shift+B),显示调用代码为
1
2
3
4
5
6
7
8
9
10
|
#include <windows.h> //使用LoadLibrary函数必须包含此头文件
#include <iostream>
using namespace std;
typedef int(*lpMul)(int, int); //定义函数指针
int main() {
lpMul myMul; //声明函数指针变量 //动态加载dll到内存
HINSTANCE hModule = LoadLibrary(L"TestDll2.dll"); //L -> 宽字符版本 -> 现代Windows兼容
myMul = (lpMul)GetProcAddress(hModule, MAKEINTRESOURCEA(13)); //通过序号获取函数地址
cout << "2*1=" << myMul(2, 1) << endl;
}
|
通过序号调用可隐藏函数名~
参考
Visual Studio 2022静态库与动态库创建及使用完全指南