1 库的概念与静态库
写程序的时候,我们希望自己写的东西具有通用性,例如函数或者结构体,不单单自己能用,别人也可以用,但有些时候,我们又不希望自己写的代码让别人看到。如何才能做到让别人看不到的情况下,也可以正常使用呢,这个时候库就出现了。源文件经过编译和汇编后,形成目标文件(后缀为.o),将由不同源文件得到的目标文件打包压缩后可以形成一个库,有了库之后,就可以给其他人使用。
库分为静态库与动态库,前者的后缀为.lib,后者的后缀为.dll
(1)VS2019静态库的创建
新建一个空项目(“空项目”就行,不需要“静态库”),名称为staticlib,新建一个名为mylib.h的文件,内容如下:
#pragma once
#ifndef TEST_H
#define TEST_H
int myadd(int a, int b);
#endif
新建一个名为mylib.c的文件,内容如下:
int myadd(int a, int b) {
return a + b;
}
再写一个测试代码,内容如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include "mylib.h"
int main() {
printf("%d", myadd(10, 20));
return 0;
}
输出
30
说明函数myadd(10, 20)
没有问题,接下来创建静态库
配置静态库需要先把main.c移除,操作如下:
在弹出的对话框中,选择“移除”,移除之后,main.c还在项目文件夹下,但不再项目里了,这里要区分“路径下存在”和“项目中存在”两个概念
右击项目名称——属性
在“配置属性——常规——配置类型”下拉选项中选择静态库,然后再点击确定
重新生成解决方案
可以在下面看到静态库的目录,它在解决方案的Debug文件夹下(是解决方案下的Debug,而不是项目下的),至此静态库创建完成
(2)静态库的使用
创建一个空项目,名为“静态库的使用”,将静态库文件staticlib.lib和头文件mylib.h拷贝到“静态库的使用”项目文件夹下:
上面只是路径放对了,但没有添加到项目中,接下来要添加到项目中。在解决方案资源管理器中,右击项目名(静态库的使用)——添加——现有项
把刚刚考过去的两个文件添加到项目“静态库的使用”中
接下来右击解决方案——属性,将启动项目设置为“当前选定内容”
好了,接下来写一段测试代码,名为main.c,内容如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include "mylib.h"
int main() {
printf("%d", myadd(10, 20));
return 0;
}
输出如下:
好了,静态库的配置结束。
其实上面在拷贝的时候,头文件(mylib.h)可以不用拷贝到“静态库的使用”目录下,但为了让别人知道接口怎么用,所以才把头文件拷过去。
如果不拷头文件,测试代码如下,在新项目中生成的时候,会有警告,说myadd未定义,但问题不大,因为只要创建了静态库,不需要声明,可以直接使用。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//#include "mylib.h" 不需要头文件,可以直接使用动态库中的函数
int main() {
printf("%d", myadd(10, 20));
return 0;
}
(3)静态库的优缺点
优点:静态库在链接阶段被复制到了可执行文件(.exe)中,也就是说,最后生成的可执行文件(.exe文件),已经包含了静态库的所有东西了,此时即便把.lib文件删了,.exe文件也能照常运行。由于静态库的机制,因此程序(.exe文件)在运行时与静态库再无瓜葛,移植方便。
缺点:首先,静态库对内存和磁盘空间浪费非常严重,因为静态库会被添加到和它连接的每个程序中, 而且这些程序运行时, 都会被加载到内存中,假如A.exe和B.exe都用到了静态库,它们都包含了静态库的所有内容,由于静态库不共享,若A.exe和B.exe同时执行,则会造成资源浪费;若静态库中的任意一个函数要修改,那么整个程序就要重新编译链接,生成新的可执行文件(.exe),因此对更新发布带来麻烦(项目越大越明显,因为静态库多,函数多,而任何一个更新都需要重新编译)。
2 动态库
静态库由于无法共享,所以会造成资源浪费和更新发布麻烦,因此与之相对的动态库诞生了。动态库的机制,请看本文的第3节。
(1)VS2019动态库的创建
新建一个空项目,名称为mydll,新建一个名为mydll.h的文件,内容如下:
#pragma once
__declspec(dllexport) int myminus(int a, int b);
新建一个名为mydll.c的文件,内容如下:
#include "mydll.h" //这句一定要写,否则无法生成.lib文件(引入库)
int myminus(int a, int b) //是否使用__declspec(dllexport) 声明,无所谓,可以加可不加
{
return a - b;
}
可以写一个main函数测试一下
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include "mydll.h"
int main() {
printf("a-b=%d\n", myminus(10, 5));
return EXIT_SUCCESS;
}
输出:
a-b=5
测试通过,接下来要创建动态库
右击项目名,选择“属性”
在“配置属性——常规——配置类型”中选择“动态库.dll”,然后确定
接下来重新生成,然后在解决方案路径下(是解决方案路径,不是项目路径)的Debug文件夹中,会有mydll.dll和mydll.lib两个文件
(2)动态库的使用
创建一个新项目,名为“动态库的使用”,然后将mydll.dll和mydll.lib两个文件复制到项目路径下,把“mydll”项目路径下的mydll.h也复制到“动态库的使用”路径下(其实mydll.h可以不用拷贝,但由于里面有函数接口,拷贝过去能让使用者更清楚函数的输入输出),此时“动态库的使用”项目路径下的内容如下:
右击“解决方案“mydll” ”,在属性页中,将启动项目设置“当前选定内容”
接下来右击项目名,添加——现有项,将刚刚复制过来的三个文件添加进来
现在的项目路径如下:
好的,现在我们成功添加了动态库,最后可以写一个测试文件,命名为main.c,内容如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#pragma comment(lib,"./mydll.lib") //告诉编译器,当前目录下有个lib文件,包含了动态库中函数、变量的声明
int main() {
printf("a-b=%d\n", myminus(10, 5));
return EXIT_SUCCESS;
}
输出
a-b=5
上面有一条语句#pragma comment(lib,"./mydll.lib")
,教程中说是需要这个,但我试了把这条语句注释掉也可以正常运行;另外,上面的程序会有警告,warning C4013: “myminus”未定义;假设外部返回 int
,但不影响使用,如果不想有警告,可以加上这么一句话:#include "mydll.h"
通过了测试。
3 静态库与动态库的比较
静态库的.lib会将所有函数声明、实现都放入其中,主调函数在使用静态库中的函数时,在链接阶段会将静态库中的所有内容复制到最后生成的可执行文件(.exe)中。因此使用静态库时,.exe文件包含了静态库中的所有内容。
动态库的.lib只会放导出函数的声明、和变量声明,因此称为引入库,在编译链接可执行文件时,只需要链接该 DLL (动态库)的引入库文件,该 DLL 中的函数代码和数据并不复制到可执行文件,直到可执行程序运行时,才去加载所需的DLL,将该 DLL 映射到进程的地址空间中,然后访问 DLL 中导出的函数(__declspec(dllexport)声明的函数)。因此,使用动态库时,.exe文件只包含了引入库中的内容。
假如A.exe和B.exe都用到了动态库,在生成A.exe和B.exe时(即编译链接阶段),只是把引入库复制了过去,未复制.dll中的内容,这使得A.exe和B.exe资源的占用比较小,A.exe和B.exe只有在运行时才会加载DLL中导出的函数(__declspec(dllexport)声明的函数)。由于这个机制,若动态库中的函数要更新,那么A.exe和B.exe无需重新编译。