什么是链接库
库(库文件):计算机中有些文件专门用于存储可以重复使用的代码块。
//函数库
int add(int a,int b)
{
return a + b;
}
上述函数库为源代码库,该库文件的二进制版本——链接库。
链接库,就是将开源的库文件进行编译、打包操作后得到的二进制文件,二进制文件无法独立运行,必须等待其他程序调用才会被载入内存。
- 编译:生成多个二进制目标文件,它们之间会相互调用对方的函数或变量,编译器无法跨文件找到对应存储地址,故无法单独执行;
- 链接:链接器修复各个目标文件中缺失的函数和变量的存储地址,并最终将所有目标文件和链接库组织成一个可执行文件。
静态链接(完成所有可执行文件链接操作再载入内存)
- 形成可执行程序前对所有 *.o 目标文件进行对应链接文件地址查找以形成可执行程序;
- 多个目标文件的形成使生成多个具有共性(同个函数)的副本,加大了容量,同时进行更新修改时需要重新进行可执行程序的重新生成,但对于可执行程序是优势的,全面的程序执行所需要的内容,使得执行速度更快。
动态链接(动态链接库随着可执行文件的需要而载入内存)
- 链接器先从所有目标文件找到对应目标文件部分缺失的地址,然后组织成一个可执行程序,待文件执行时,连同所有的链接库文件一起载入内存,再由链接器完成剩余的地址修复工作,从而正常执行。
优点:
- 节省内存和代码重用:多个应用程序共享磁盘上单个DLL副本;
- 可扩展性:接口不变;
- 更新程序只需更新DLL文件;
- 复用性:不同语言编写的程序只需要按照函数调用约定就可以调用同个DLL函数。
链接库的区别
静态链接库
*.h 文件和 .lib文件
:
- *.h 文件:函数声明与参数定义等;
- *.lib 文件:包含函数的索引以及实现等;(Linux下是 .so 文件)
- 语法:(预处理阶段就被替换)
#include"头文件" #pragma comment(lib, "./头文件.lib")
动态链接库
*.h 文件、 *.lib 文件、 *.dll 文件
:
- *.lib 文件:存储链接程序所必须的信息,并没有存储函数的实现等;
- *.dll 文件:存储函数的具体实现,程序运行加载时载入内存以充分运行;
*.dll 文件存放位置
:
- 解决方案下的 debug 文件夹,同程序 exe 一块;
- 与 .h .lib .dll .cpp 文件同个位置;
- 系统目录下:system32 或 system64 文件夹;
- 环境变量所列出的路径下。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
BOOL APIENTRY DllMain(HMODULE hModule, //模块调用
DWORD ul_reason_for_call, //调用原因
LPVOID lpReserved //保留参数 不用关心
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: //被其他程序加载时候
MessageBox(NULL, L"加减乘除DLL被加载", L"l love Mark", MB_OK);
break;
case DLL_THREAD_ATTACH: //当其他程序启动了一个线程的时候
break;
case DLL_THREAD_DETACH: //当其他程序某个线程线程终止运行的时候
break;
case DLL_PROCESS_DETACH: //被其他程序卸载的时候
MessageBox(NULL, L"加减乘除DLL被卸载", L"l love Mark", MB_OK);
break;
}
return TRUE;
}
某个动态链接库的头文件:
#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport)
#endif
//所有的函数一定要设置成为导出类型,否则无法生成lib文件,不存在导出的函数
DLL1_API double add(double a, double b);
DLL1_API double subtract(double a, double b);
DLL1_API double multiply(double a, double b);
DLL1_API double chu(double a, double b);
class DLL1_API Point
{
public:
void output(int x, int y);
};
使用链接库
静态链接库
1、.lib 文件和 .h 文件:同时包含到使用项目下,再进行代码调用: #include "static.h" #pragma comment(lib, "static.lib")。
2、将头文件 .h 放进自定义路径 include 包含目录,
将库文件 .lib 放进自定义路径 lib 库目录,
选中项目进行属性“VC++ 目录 —— 常规”的包含目录和库目录的路径编辑添加,
同时“链接器 —— 输入”的附加依赖项添加 .lib 文件全名,
回到代码调用:添加对应静态库指定的头文件 #include "static.h"。
3、(1)引用添加 .lib 文件,
(2)在项目属性“C/C++ —— 常规”的附加包含目录下添加 .lib 文件目录,
(3)回到代码调用:添加对应静态库指定的头文件 #include "static.h"。
动态链接库
1、隐式调用:需要 .h .lib .dll
(1)将 DLL 过程中的 .h、.lib以及 .dll 文件复制到与项目同名文件夹下;
(2)项目添加上述文件,再进行代码调用:
#include "MyDll.h" #pragma comment(lib,"MyDll.lib")
或者:
(1)在项目属性“C/C++ —— 常规”的附加包含目录下添加 .lib 文件目录, (2)在项目属性“链接器 —— 输入”的附加依赖项下添加 .lib 文件全名, (3)在项目属性“链接器 —— 常规”的附加库目录下添加 .lib 文件的完全路径, (4)回到代码调用:
/#include "MyDll.h"
2、显式调用:只需要 .dll
(1)创建别名,实现将动态库函数导出
(2)定义实例句柄,获取 DLL 的实例地址,再引用动态库
(3)加载动态库路径,写入句柄(取的地址要判断,确认是否返回为空 / 无效句柄)
(4)定义函数指针,指向动态库路径导出函数
(5)获取动态库对应函数名称的地址,使得函数指针正确指向该函数
(6)判断函数指针是否有效,有效则进入函数调用
(7)释放动态句柄
#include <iostream>
#include <Windows.h>
using namespace std;
// 1、创建别名,引用函数类型需要和 dll 中的导出函数一致
typedef int (*LPDO_ARRAY)(int* pArr, int nlen);
int main()
{
// 定义实例句柄,引用动态库
HINSTANCE hDll;
// 定义函数指针,指向导出函数
LPDO_ARRAY lpDo_array;
// 2、加载动态库文件
hDll = LoadLibrary(L"BuildDllDemo1.dll");
// 3、获取 dll 中函数的地址
lpDo_array = (LPDO_ARRAY)GetProcAddress(hDll, "convert_array");
if (lpDo_array != NULL || lpDo_array != 0)
{
// 4、调用函数
/// ...TODO
// 5、释放动态库句柄
FreeLibrary(hDll);
}
return 0;
}
动态链接库的导出
宏定义
#ifdef XXX_DLL_EXPORTS
#define XXX_DLL_API __declspec(dllexport)
#else
#define XXX_DLL_API __declspec(dllimport)
#endif
导出类
1、__declspec(dllexport)
方式简单,导出的内容多,使用着对类的实现依赖太多,还需保证使用同一编译器。
本质:导出类里面的函数,因为语法上直接导出了类,没有对函数的调用方式和重命名进行设置,导致了 dll 并不通用。
struct CEMPLOYEETOMOM_DLL_API CEMPLOYEETOMOM_STT
{
int a;
}
class XXX_DLL_API CEmployeeToMOM
{
private:
...
public:
...
}
2、对象类指针接口
定义抽象类(纯虚函数),调用者跟 dll 共用一个抽象类的头文件。
DLL 实现抽象类的派生类,最少只需要提供一个用于获取抽象类对象指针的接口。
/// a.h
class VirtualBin
{
public:
VirtualBin() {}
virtual ~VirtualBin(){}
virtual double Add(double a, double b) = 0;
};
/// a1.h
class BinClass:public VirtualBin
{
public:
double Add(double a, double b);
};
/// a1.cpp
double BinClass::Add(double a, double b)
{
return a + b;
}
BinClass* __stdcall CreateClass(const char * s)
{
if (_stricmp(s, ("BinClass")) == 0)
{
return new BinClass;
}
return NULL;
}