文章目录
一、概念
不开源的库,其源代码是不可见的,开发者只能在暴露的头文件中,调用它对外的接口。库是源代码的二进制文件。
1、静态库
静态链接库是一个或多个.obj文件的打包,即将多个源代码文件编译成目标文件后,再将这些目标文件合并成一个库文件。这些目标文件包含了实际执行代码、符号表等。
静态库在编译链接阶段
,链接器会将静态库中的代码直接复制到生成的可执行文件中。因此,可执行文件中包含了库代码的一份完整拷贝。在程序运行时,不需要静态库文件。
- 优点
由于源代码已经嵌入在可执行程序中,所以运行速度快,可以独立运行。- 缺点
可执行程序中(.exe)包含了库代码的一份完整拷贝,增加了可执行文件的大小。其他程序使用后,系统中就会有多份冗余拷贝,消耗了内存。而且程序实现发生变化,需要重新编译和链接整个应用程序。
使用场景:不频繁更新,依赖的基础库稳定。
2、动态库
动态链接库在运行时调用。
它包含两种加载方式。
一种是隐式链接(自动加载.dll),导入库是必须存在,运行时,程序会自动加载对应的dll文件。这种方式代码结构更简单。隐式链接的动态库也可以独立更新,前提是需要保证dll导出的函数和符号保持兼容,不能删除或修改已存在的,函数的参数和返回类型也需要一样,如果结构体在库之间的传递时,结构体大小和内存布局变化也会导致问题,但是你可以新增,或者重新优化接口里面的代码。
一种是显式链接(手动加载.dll),编译时不需要导入库。通过Windows API(如LoadLibrary)运行时手动加载库,并获取函数的地址(函数指针)。可以在运行时决定是否加载动态库,也可以在不重新编译程序的情况下替换DLL,灵活性更高。还可以防止程序刚开始动态库自动加载过多。但是失去了编译时的类型检查,需要手动管理动态库的加载和函数获取。
- 优点
因为多个程序可以共享同一个动态库,系统在内存中只需要加载一份,这样节省了内存和磁盘空间,并且便于升级和维护,只需更新动态库即可影响所有引用该库的程序。并且可执行文件也更小。- 缺点
程序运行时,需要依赖动态库文件,如果没有则程序无法运行。还可能存在版本兼容性问题,不同版本的动态库可能不兼容。
使用场景:该库经常更新,快速迭代,其他进程也依赖。
3、导入库
导入库是动态链接库(DLL)的辅助库,其扩展名也为.lib,但和静态库的功能和内容都不相同。作用是为了让程序在编译时找到动态库中的函数的位置
,辅助程序在运行时找到并调用DLL中的函数。使加载和使用动态库的过程自动化。
导入库在程序运行时就不需要了,当DLL的代码生成时,链接程序会查找函数或C++类的信息,并自动生成一个.lib文件,即导入库。它包含了函数和符号的地址,不包含实际执行的代码,代码是位于动态库(DLL)中的。当程序在编译和链接阶段时,导入库会解析DLL中函数和符号的地址,确保程序在运行的时候能够正确调用DLL中的函数。
linux没有单独的导入库,.so既是动态库也是导入库
。windows需不需要导入库,取决于动态库的加载方式。
- 优点
程序无须手动加载动态库,dll可以独立更新而无需重新编译程序。- 缺点
编译时必须有导入库,如果运行时找不到dll文件,程序无法运行。
二、生成库的文件类型
1、windows
.lib 静态库/导入库
.dll 动态库
2、linux
.a 静态库
.so 动态库
三、Windows创建静态库和动态库
该示例通过 VS 创建库文件。解决方案右键,选择添加,选择新建项目,搜索模版dll,点击下一步创建。
1、静态库
2、动态库
需要手动加载。假设我们有一个DLL(比如example.dll),它包含一个名为AddNumbers的函数,该函数接受两个整数参数并返回它们的和。
#include <windows.h>
typedef int (*AddFunc)(int, int); // 声明函数指针类型
int main() {
HMODULE hMod = LoadLibrary("example.dll");
// 获取函数指针
AddFunc addFunc = (AddFunc)GetProcAddress(hMod, "AddNumbers");
int result = addFunc(5, 3);
FreeLibrary(hMod);
return 0;
}
3、具有导出项的动态库
也就是包含导入库的动态库,可以将类、变量、函数进行导出。主要涉及微软C++两个关键字。linux下也有自己的导出关键字,感兴趣可以自行研究。
导出关键字(__declspec(dllexport)):
相对于DLL来说,需要将函数等导出,外部程序可调用。告诉编译器,生成 DLL时,这些符号应该被包含在 DLL的导出表中。
导入关键字(__declspec(dllimport)):
相对于外部程序来说,表示可以使用DLL的接口。告诉编译器,这些符号是在另一个 DLL中定义的,编译器应该生成必要的代码来从 DLL导入这些符号。在实践中,通常不需要显式地在每个导入的符号前使用 __declspec(dllimport),因为编译器在链接到 DLL 的导入库(.lib 文件)时会自动处理这一点。
extern "C"
主要用来告诉编译器,函数命名风格按照C语言的形式命令,为了保持兼容性,C语言也可以使用C++库函数。
// dll导出纯虚函数接口类对象指针,方便调用虚函数成员。
extern "C" __declspec(dllexport) IEIPClient *CreateEIPClientObject();
extern "C" __declspec(dllexport) int calAddSub(void); // 导出函数
extern "C" __declspec(dllexport) int nTotal; // 导出变量
// 外部程序调用dll中的函数时,编译时需要先添加动态库的导出库的路径,再声明函数。将dll和它的导出库lib放在同一个路径下。
// 运行程序只需要dll库即可,导入库就需要了。
#pragma comment(lib, "../x64/debug/3rdParty/image/bin/imageParse_d.lib");
// 但是导入函数由编译器自主导入,可省略
extern "C" __declspec(dllimport) int calAddSub(void); // 不需要
...
四、Linux下创建静态库和动态库
1、静态库
首先生成目标文件.o,使用-c选项,通过ar去生成静态库。可以看到静态库包含哪些源文件。然后在编译main.c时,链接静态库。
//生成静态库,lib和.a是库名的前缀和后缀。ar:gnu的归档工具,rc:取代并创建。
ar -rc libmymath.a add.o sub.o mul.o div.o
//查看静态库
ar -tv libmymath.a
t:列出静态库中的文件
v:详细信息
//使用静态库
gcc main.c -L. -lmymath
-L:指定库路径
-l:指定库名,库名不包含前缀和后缀。
2、动态库
//-fPIC
表明使用地址无关码,使共享物理内存不被修改。
//-shared
表示产生共享库
//ldconfig可以进行配置,使新安装的动态库为系统所共享。然后使用ldconfig进行更新。
五、Windows下查看动态库
1、DLL文件是32位还是64位?
(1)查看文件
通过文本方式打开动态库,在第二段中找到
PE两个字母
,后面会出现字母d或者L
。
- d 64位
- L 32位
(2)命令查询
dumpbin.exe /headers math.dll