Windows下静态库与动态库的创建与使用
Windows下静态库与动态库的创建与使用
学习内容:本博客介绍了Windows下使用Visual C++ 6.0制作与使用静态库与动态库的方法。
————————CONTENTS————————
一.什么是静态库与动态库?
库(Library)是可以复用的代码,在一些大型项目中常常会用到库。
本质上说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。
在前期的课程中,我们学习了Linux下静态库与动态库的使用,Windows下与之对应:
- 静态库:linux下.a文件、windows下.lib文件
- 动态库:linux下.so文件、windows下.dll文件
所谓静态、动态是指链接。回顾一下将一个程序编译成可执行文件的步骤:
静态库与动态库的区别就在于,【链接阶段】如何处理库,从而连接成可执行程序。
之所以成为【静态库】,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
试想一下,静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。
静态库的特点可总结为:
- 静态库对函数库的链接是放在编译时期完成的。
- 程序在运行时与函数库再无瓜葛,移植方便。
- 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
Linux下使用ar工具、Windows下使用lib.exe,将目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索。一般创建静态库的步骤如图所示:
通过上面的介绍发现静态库,容易使用和理解,也达到了代码复用的目的,那为什么还需要动态库呢?
为什么需要动态库,其实也是静态库的特点导致。
- 空间浪费是静态库的一个问题。
- 另一个问题是静态库会对程序的更新、部署和发布带来麻烦。如果静态库lib.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
动态库特点总结如下:
- 动态库把对一些库函数的链接载入推迟到程序运行的时期。
- 可以实现进程之间的资源共享(因此动态库也称为共享库)
- 将一些程序升级变得简单。
- 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显式调用)。
综上所述,静态库与动态库的不同点在于:代码被载入的时刻不同。
- 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。
- 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小。
- 动态库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
二.静态库的创建与使用
1.我们先来完成准备工作。打开Visual C++ 6.0,点击左上角/File->New...
,选择需要新建的对象:
选择Workspaces
选项卡,输入Workspaces name
以及Location
,新建一个工作区:
本篇博客所用到的Project都建在这个工作区就可以啦~
2.选中我们新建的workspace,右击选择Add New Project to Workspace...
我们先尝试创建静态库,因此在Project选项卡选择Win32 Static Library
,并在右侧输入Project name
即可完成项目创建。
接下来一路next,一个Win32 Static Library
格式的Project就建成了:
3.注意到一个工程下面有两个文件夹,分别是Source Files
和Header Files
。顾名思义,Source Files
用来存放源文件,如.cpp
等;Header Files
则用来存放头文件。
我们先来建一个头文件,存放函数说明。
选中Header Files
文件夹,点击File->New...->Files->C/C++ Header File
,并输入文件名,完成创建。
内容为:
int statlib_demo(int a, int b);
4.同样的方法,在Source Files
中新建staticlib.cpp
,这次需要选中C++ Source File
。
内容为:
#include "staticlib.h"
int statlib_demo(int a, int b)
{
if (a > b)
return a-b;
else
return a+b;
}
就此完成了代码基本的编写。
5.选择这个Project,依次点击Compile
和Build
按钮:
打开Project所在文件夹中的Debug
文件夹,可以看到成功生成lib.lib
,这就是我们需要的静态库啦~
接下来我们编写代码,对生成的静态库进行测试。
1.与前面类似,这次在workspace中新建一个Win32 Console Application
的Project:
选择A "Hello, world!" application.
:
可以看到在工作区生成了一个名为libTest
的Project,其中包含一些自动生成的文件和代码:
2.将刚才生成的lib.lib
拷贝到新建的这个项目文件夹下,以进行后续的调用。
3.修改这个Project中的代码,完成对静态库调用的测试:
libTest.cpp
:
#include "stdafx.h"
#include "../lib/staticlib.h" //要注意在这里添加头文件的位置
int main(int argc, char* argv[])
{
printf("Hello World!\n");
int iRet;
iRet = statlib_demo(3,5);
printf("the result of value is %x\n",iRet);
return 0;
}
StdAfx.h
:只需要在#include <stdio.h>
下一行加上#pragma comment (lib,"lib")
即可。其中,第一个“lib”代表链接一个lib库,第二个“lib”是这个库的名字,需根据实际情况修改。
StdAfx.cpp
不需要做改动。
4.完成了以上代码的编写和修改,Build
这个项目,并Execute Program
就可以了。运行结果如下:
到此为止,我们完成了静态库的编写和测试。最关键的点就是#pragma comment(lib,"XXX")
这句预处理指令了。
静态链接时,编译器将函数和过程都编译到exe文件,且函数的相对位置在链接时已经确定。多个程序调用同一个函数时,内存中保存多份函数。
三.动态库的创建与使用
DLL(Dynamic Linkable Library),动态链接库,可以向程序提供一些函数、变量或类。这些可以直接拿来使用。
DLL中一般定义有两种类型的函数:导出函数和内部函数。其中:
- 导出函数可以被外部程序调用;
- 内部函数只能在DLL内部使用。
为了使生成的dll文件可以被外部程序调用,我们主要关注导出函数的生成与使用。
从dll中声明导出函数有两种方式:
- (1)用模块定义(.def) 文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息;
- (2) 用__declspec(dllexport)来声明函数
下面我们来详细了解这两种方式的用法。
一、使用.def文件
1.同样地,在workspace新建一个Project,选择为Win32 Dynamic-Link Library
:
选择A simple DLL project.
:
可以看到在工作区生成了一个名为dll_def
的Project,其中包含一些自动生成的文件和代码:
2.修改这个Project中的代码,完成动态库的编写:
dll_def.cpp
:
#include "stdafx.h"
#include "dll_def.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
int cmp3_demo(int a, int b)
{
if (a > b)
return a;
else
return b;
}
在Header Files
中新建一个dll_def.h
,内容为:
int cmp3_demo(int a, int b);
其他已存在的文件不需要做修改。
3.最关键的步骤到了!!!——编写.def
文件。
- DEF文件是一个包含EXE文件或DLL文件声明的文本文件;
- 每个.DEF文件至少必须包含LIBRARY语句和EXPORTS语句
.def文件的规则为:
- LIBRARY语句说明.def文件相应的DLL;
- EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用);
- .def 文件中的注释由每个注释行开始处的分号 (😉 指定,且注释不能与语句共享一行。
针对我们这个Project,需要在Source Files
中新建一个dll_def.def
文件,内容为:
LIBRARY DLL_DEF
EXPORTS
cmp3_demo
4.选择这个Project,依次点击Compile
和Build
按钮,打开Project所在文件夹中的Debug
文件夹,可以看到成功生成dll_def.dll
,由此完成了动态库的生成。
二、使用关键字_declspec(dllexport)
下面我们尝试使用第二种方法生成dll文件。
1.在workspace新建一个Project,选择为Win32 Dynamic-Link Library
,具体过程不再赘述。
2.修改这个Project中的代码,完成动态库的编写:
dll_decl.cpp
:
#include "stdafx.h"
#include "dll_decl.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
LIB_API int cmp_demo(int a, int b)
{
if (a < b)
return a;
else
return b;
}
其余已存在的文件中的代码不需要做修改。
3.这个方法最关键的一步!!!——编写dll_decl.h
头文件。
导出函数的声明时需要有_declspec(dllexport)关键字。具体形式为:
_declspec(dllexport) <返回类型> <导出函数名> (<函数参数>);
在本Project中,编写的代码如下:
#define LIB_API __declspec(dllexport)
LIB_API int cmp_demo(int a, int b);
4.与上文同样的步骤,选择这个Project,依次点击Compile
和Build
按钮,打开Project所在文件夹中的Debug
文件夹,可以看到成功生成dll_decl.dll
,由此使用第二种方法,完成了动态库的生成:
- 动态链接库加载分为静态加载(隐式链接)和动态加载(显式链接):
- 静态加载方式是在编译程序代码时即完成对DLL的加载,应用程序结束时卸载DLL。
- 动态加载方式是使用用API函数来加载和卸载DLL的。
一、隐式链接
1.在workspace中新建一个Win32 Console Application
的Project,名为dllstaticTest
。首先,需要把之前生成的dll文件和对应的lib文件拷到这个文件夹下,以完成后续调用。
2.隐式链接动态库主要有三种方法:
①将lib文件直接加入新建的这个测试project中。
②在测试程序所在project的StdAfx.h文件中增加#pragma comment (lib,"XXX")
实现。
③在本Project下的“project”菜单的“setting”下对话框中选择这个工程,并在对应的“Link”选项窗口中的“Object/library modules”下增加需要导入的lib文件:
3.修改这个Project中的代码,完成动态库的测试:
dllstaticTest.cpp
:
#include "stdafx.h"
#include "../dll_decl/dll_decl.h"
int main(int argc, char* argv[])
{
int iRet;
printf("Hello World!\n");
iRet = cmp_demo(3,5);
printf("return value is: %x\n",iRet);
return 0;
}
二、显式链接
上面介绍的动态库使用方法和静态库类似,属于隐式链接,编译的时候指定相应的库和查找路径。其实,动态库还通过调用API,如:LoadLibrary、GetProcAddress、FreeLibrary完成动态调用(即显式链接)。
应用程序必须进行函数调用以在运行时显式加载 DLL。为显式链接到 DLL,应用程序需通过调用函数完成以下步骤:
- 首先调用LoadLibrary()函数加载DLL并得到模块句柄;
- 使用句柄调用GetProcAddress()函数获取导出函数指针,使用指针调用导出函数;
- 调用FreeLibrary()函数释放加载DLL。
代码如下所示:
#include "stdafx.h"
#include <windows.h>
#include "../dll_def/dll_def.h"
int main(int argc, char* argv[])
{
printf("Hello World!\n");
//LoadLibrary函数装载dll
HMODULE hmod = LoadLibrary("dll_def.dll");
if(!hmod)
{
printf("load library failed\n");
return 0;
}
typedef int (*LoadProc)(int x, int y);
//GetProcAddress获得"cmp3_demo"函数地址
LoadProc Load_proc = (LoadProc)GetProcAddress(hmod,"cmp3_demo");
if(!Load_proc)
{
printf("load function cmp3_demo failed\n");
return 0;
}
//通过函数指针间接调用"cmp3_demo"
int iRet = Load_proc(3,5);
printf("the cmp3_demo the value is:%x\n",iRet);
//释放dll
FreeLibrary(hmod);
return 0;
}