一,链接库概念详解
前言
首先,编程时,我们将存储可以重复使用的代码块文件称为库文件。比如c++常常导入的<stdio.h>,<match.h>等,这些都简称为库。根据应用程序使用库时的链接方式被划分为静态链接库
和动态链接库
两种。都符合PE文件格式
下面为大家详细介绍。
1,静态链接库
(一)库格式
静态链接库(
Static-Link Library
)通常是以".lib
"作为文件拓展名的二进制文件, 也可以是以".a"等;
(二)库内容
静态链接库在生成可执行文件前,将
目标文件(.obj)
,运行时的函数库(.lib)
,已编译的资源文件(.res)
等全部链接到了一起,打包成二进制文件。该文件包含运行时所需的所有代码。因此静态链接库是可以独立运行
的(编译时加载);
包含文件:
xxx.h
:生成静态链接库时的头文件,包含方法的声明;
xxx.lib
:静态链接库,包含了一个或多个方法的具体实现代码的二进制文件;
(三)优,缺点
优点: 包含了全部运行时所需代码,可以独立运行移植性性强;
缺点: 若程序多次调用静态库中的同一代码块,则生成的可执行文件会重复加载此代码段,造成代码冗余,程序占用内存增大;
2,动态链接库
(一)库格式
动态链接库(
Dynamic-Link Library
)通常是以".dll
"作为文件拓展名的二进文件,也可以是以".so",“.sys”,".drv"等;
(二)库内容
动态链接库在生成可执行文件前,将目标文件(.obj),运行时的函数库(.lib),已编译的资源文件(.res)等
地址信息
全部记录到文件中,打包成二进制文件。该文件包含运行时所需的代码块位置信息
。因此动态链接库是不可以独立运行
的(运行时加载);
包含
xxx.h
:生成动态链接库时的头文件,包含方法的声明;
xxx.lib
:动态链接库的导入库;包含了各个函数的地址信息;没有实现代码;
xxx.dll
:动态链接库,包含了一个或多个方法的具体实现代码的二进制文件;
(三)优,缺点
优点:
1,若程序多次调用动态库中的同一代码块地址,则生成的可执行文件会加载相同地址的代码块,即使用的是同一块代码(共享代码);真正实现了运行时加载
;同时避免了空间上的浪费
;
缺点:
1,仅包含了全部运行时所需代码块地址,需要搭配相应的库文件。不可以独立运行,即移植性性强;
2,运行时加载代码块,需要通过动态库记录的地址去寻找相应地代码块,会使得程序性能下降
(约%5);
二,链接库的创建
1,链接库创建环境
系统 : windows 10专业版
编译器: Microsoft Visual Studio 2015
语言:C++
2,链接库项目创建
步骤1:创建一个空项目。操作路径为:文件-》新建-》项目;
然后选择模板中选择Visual c++ -》空项目 -》输入名称 - 》点击【确定】按钮
步骤2:点击视图-》解决方案管理器-》右键项目名称:MyLib->点击属性-》进行项目设置
步骤3:到此,任意选择上述配置,编译器编译出来的项目结果就是链接库;下面以静态库创建操作为例,如下图所示:
静态库创建
:在项目MyLib属性页中,点击常规-》配置类型-》静态库(.lib)
动态库创建
:在项目MyLib属性页中,点击常规-》配置类型-》动态库(.dll)
3,链接库功能编写
首先,在项目目录下,右键源文件-》添加》新建项-》
下面以添加.h为例,操作如下图所示,(“.h”,".cpp"文件都需要添加)
添加.h文件
:选择Visual C++ --》头文件(.h) -》 输入文件名–》点击【添加】即可
添加.cpp文件
:选择Visual C++ --》c++文件 (.cpp)-》 输入文件名–》点击【添加】即可;
静态链接库文件编写:静态链接库没有
main()
函数,其余与正常程序编写一致
动态链接库文件编写:动态链接库没有
main()
函数,区分导入导出函数
;其余与正常程序编写一致;
导入函数
:指的是链接库自身使用的函数,不能直接被外部程序直接调用;
导出函数
:指的是链接库提供给外部程序使用的函数,也可以调用自身的函数
(备注:
1,若没有在函数声明前添加关键字__declspec(dllexport)
,则默认为导入函数,不生成lib库;
2,建议使用extern "C" __declspec(dllexport)
来定义声明的函数,避免显示调用时,函数名因编译器处理c++重载导致乱码照成调用不到的情况;)
4,链接库生成
静态链接库生成
:
右键项目-》生成-》即可在输出窗口找到生成静态链接库保存路径;则生成如下".lib
"文件;
".lib"文件
: 存放链接库中函数的声明与具体代码实现等全部信息;(注意区分动态链接库的".lib"文件)
动态链接库
生成:
若在链接库项目创建时,选择动态链接库创建
,则生成如下".dll
"和“.lib
”两个文件;
".lib"文件
: 存放添加关键字__declspec(dllexport)的函数地址信息,成为动态链接库的导入库;若没有编写导出函数,则不生成".lib"文件(此时只能通过用WIN32 API函数LoadLibrary、GetProcAddress装载调用函数);
".dll"文件
: 存放添加关键字__declspec(dllexport)的函数具体实现代码信息
三,链接库的使用
在执行程序调用链接库时,需要提供链接库的相关文件如下:
静态链接库文件如下(注意:
debug
模式下生成的lib库,只能在debug模式下的程序调用,只能隐式加载
):
MyFunction.h
MyLib.lib
动态链接库文件如下(注意:
debug
模式下生成的lib库,只能在debug模式下的程序调用,可以隐式加载
,也可以显式加载
):
MyFunction.h
MyLib.lib (若动态库无此文件,则只能使用显式加载)
MyLib.dll
1,使用方式一:代码配置后使用(隐式加载
)
步骤1:创建一个可执行程序项目,用于调用链接库;
(具体流程按照链接库项目创建的步骤一执行即可(编译器默认生成exe文件),此处省略;)
步骤2:将链接库的相关文件放到刚创建的可执行程序项目文件夹中;
步骤3:创建一个.cpp文件,然后代码导入链接库的MyFunction.h
,MyLib.lib
后即可直接使用链接库中的功能函数;
步骤4:直接点击键盘快捷键【F5】或者单击运行,调用链接库成功,结果如下:
2,使用方式二:编译器配置后使用(隐式加载
)
步骤1:创建一个可执行程序项目,用于调用链接库;
(具体流程按照链接库项目创建的步骤一执行即可(编译器默认生成exe文件),此处省略;)
将MyLib.dll放在可执行程序.exe的同级目录,加载时可以直接连接到;交付用户时,只需要提供可执行程序和Mylib.dll文件即可;
步骤2:右键项目TEST–>属性–》VC++目录–>包含目录-》编辑-》新行–》选择头文件目录(要让可执行程序可以找到链接库的头文件,所以添加的包含目录是头文件MyFunction.h
所在目录;可以放在自己创建的文件夹里;)
步骤3:右键项目TEST–>属性–》VC++目录–>库目录-》编辑-》新行–》选择库文件目录–》点击【确认】即可;(要让可执行程序可以找到链接库的库文件,所以添加的库目录是库文件MMyLib.lib
所在目录)
步骤4:右键项目TEST–>属性–》链接器-》输入-》附加依赖项-》编辑–》输入库名称MyLib.lib
–》点击【确认】即可;(要让可执行程序可以找到链接库的库文件,所以添加的库目录是库文件MMyLib.lib
所在目录)
步骤5:创建一个.cpp文件,然后代码导入链接库的MyFunction.h
后,可直接使用链接库中的功能函数;
步骤6:直接点击键盘快捷键【F5】或者单击运行,调用链接库成功,结果如下:
3,使用方式三:函数指针直接使用(显式加载
)
显式加载指的是程序运行,使用到动态库时,在通过LoadLibrary
方法调用dll文件;
然后通过GetProcAddress
方法获取dll文件中的函数地址;最后通过函数指针实现库方法的调用;(无需动态库的导入库lib文件);
步骤1:创建一个可执行程序项目,用于调用链接库;
(具体流程按照链接库项目创建的步骤一执行即可(编译器默认生成exe文件),此处省略;)
步骤2:将对应的库文件放到调用库的项目中去;
将动态库的头文件直接放在test.cpp的同级文件夹目录下,直接用
#include
通过相对路径
导入即可。省去配置头文件的环境;
将动态库的dll文件直接放在可执行程序exe的同级文件夹目录下,运行时,程序会自行链接,省去配置动态库的环境了;
步骤3:通过API实现dll的加载即函数调用,代码如下
#include <stdio.h>
#include <stdlib.h>
#include <wtypes.h>
#include <WinBase.h>
#include "MyFunction.h" //步骤一:导入静态库对应的.h文件(相对于test.cpp路径)
//#pragma comment(lib,"MyLib.lib") //步骤二:导入静态库(相对于test.cpp路径)
int main()
{
//指定的可执行模块映射到调用进程的地址空间
HINSTANCE hDLLDrv = LoadLibrary("MyLib.dll");
if (hDLLDrv == NULL){
printf("动态链接库 MyLib.dll 加载失败\n");
system("pause");
return 0;
}
//定义一个FunAdd函数的指针类型pFunAddr
typedef int(*pFunAddress)(int x, int y);
//根据函数名称获取函数地址
pFunAddress pFunAdd = (pFunAddress)GetProcAddress(hDLLDrv, "FunAdd");
if (pFunAdd == NULL){
printf("动态链接库没有找到函数FunAdd的地址\n");
system("pause");
return 0;
}
//直接使用dll库中的函数,等价于int iValue = FunAdd(1, 3);
int iValue = pFunAdd(1, 3);
printf("FunAdd return: %d\n", iValue);
//释放函数对dll文件的引用
FreeLibrary(hDLLDrv);
system("pause");
return 0;
}
运行结果如下:
一,链接库总结
写着写着,发现链接库的内容也不少啊。作为学习关键的一部,总结时不能少的啦,所以下面我来说说链接库的一些容易弄错的点吧;
首先,.lib
文件在静态链库中是一个有完整代码实现的二进制文件;而在动态链接库中是只包含了函数地址信息的导入库,就是一个壳,实际代码实现在.dll文件中;
然后,显式加载
相当于做了lib导入库的工作,使用显式加载时,lib文件便可有可无了;
还有延迟加载等,这些实现原理与显示加载大致一样;其余没讲到的地方还请大家见谅,学海无涯嘛,不过我会继续向前,修习自己的道,请大家一起见证哈。