C++ 中的动态库和静态库(Windows)
库:
在C/C++中,使用库(Library)的技术,可以将编译好的符号提供给第三方使用。
库有两种:
1、动态库 Dynamic-Link Library (DLL) (Linux下叫做 Shared Library)
2、静态库 Static Library
一、动态库的创建和使用
创建DLL:
用VC创建一个类型为 "dll"的控制台项目,VC会自动创建DLL的项目框架
它自动生成一个DllMain函数,可以类比普通应用程序中的main函数
VC项目设置:
1、取消"预编译头文件"
2、改为 "/MTd编译"
3、修改输出的DLL的名字 (my.dll)
编译,得到 *.lib 和 *.dll,其中:
*.dll:
包含所有代码编译成的指令
*.lib:包含一个列表,表名my.dll中含有哪些符号,每个符号对应在dll中的位置。(被导出的符号)
所以,*.lib比*.dll的文件体积小得多
如果想导出一个全局函数,就用关键字 __declspec(dllexport)来声明
注意:这是VC平台特有的关键字,在linux平台下不可用
使用如下:
template__declspec(dllexport) void MySwap(T& obj1, T& obj2){ T tmp = obj1; obj1 = obj2; obj2 = tml;}
使用dll:
#pragma comment(lib, "12_18_DLL01")__declspec(dllimport) int Add(int a, int b);int main(){ int ret = Add(1, 2); std::cout << ret << std::endl; return 0;}
但这个地方我发现了一个问题:
__declspec(dllimport) int Add(int a, int b);
这行代码表示导入dll文件中的符号
但一开始我错误得写成了 export, 结果居然仍然是正确的
没有深究,初步推测是编译器优化了 (IDE: VS2015),甚至不写前面的关键字,直接写成函数的声明,程序也是能正确执行的
DLL的部署位置:
1、可执行文件所在目录
2、进程当前目录
3、系统目录 (C:\Windows\System32\ 和 C:\Windows\System\)
4、Windows目录 (C:\Windows\)
5、环境变量 PATH 中的目录
通过初步接触DLL的发布和使用,相比普通的声明、定义,会发现DLL有以下作用:
1、隐藏了代码
2、公开了功能
当然,DLL的作用远不止如此
二、DLL的加载和卸载
DLL的加载:
DLL不能独立运行,只有当 *.exe 被运行时, *.dll 才会被加载运行。
在exe文件中有一些标识信息,表示该 exe 依赖哪些 dll 文件,操作系统据此去寻找、加载相应的dll文件。
exe 被加载到内存,形成一个进程 process,dll也被加载到内存,进程可以直接调用DLL中的函数(Code)
数据段和代码段:
在dll文件中,至少分为两个段:
1、代码段:
存储指令(函数体)
2、数据段:
数据段,存放全局变量
当 *.dll 被加载时,代码段只被加载一次,是公共的。
数据段被每个程序各自拷贝一份,是私有的
三、DLL中的动态内存管理
在dll中申请的动态内存,必须在dll中释放,否则会导致内存泄漏 (这是由Windows自己的特点决定的)
四、DLL中使用头文件
按照惯例,由模块的作者提供头文件,头文件中应该用类型、函数声明。
所以,我们在创建DLL时,应该附带一份头文件,而不是让使用者自己去写函数声明。
五、DLL中导出一个类
导出类的定义,其实就是导出其成员函数
类的声明格式:
class __declspec(dllimport) MyClass{};
六、静态库的概念及使用
静态库:
static library,仅一个 *.lib 文件
静态库中直接就含有代码段和数据段,在链接过程中,是直接把里面的东西链接过来,形成完整的可执行程序
exe运行的时候不依赖 .lib 文件
创建:
1、建立一个新工程,工程类型为:静态库
2、不需要其他设置,直接编译链接生成,得到 *.lib文件
使用:
#pragma comment(lib, "MyLib.lib")int MyAdd(int a, int b); //通常发布会把把声明放到头文件中,一并发布
静态库的注意事项:
1、静态库的使用不太方便:
如果该静态库是VS2008编译的,那么APP也得用VS2008编译,版本必须一致
此外,运行时库(/MT、/MTd、/MD、/MDd)也必须要一致
因此可以看出,静态库的使用,约束条件是比较多的
静态库和动态库的对比:
静态库优点:
使用静态库,最后得到的可执行程序执行时对这个库不再依赖
动态库优点:
便于升级更新,只要保持接口不变,可以通过更新DLL来升级程序,而不需要重新编译程序
目前通常都使用动态库
但动态库也存在更多的安全方面的隐患,毕竟如果有人恶意替换DLL(能做到这一步的人往往是软件团队的内部人员),程序的执行将会违背编程者的本意.......
七、DLL的手动加载
之前加载DLL的方式是自动加载。
自动加载和手动加载:
1、自动加载
在编译时指定dll,则当exe程序启动运行时,首先加载相关的dll
(DLL在程序执行前被加载,在程序结束后被卸载)
2、手动加载
在编译时不指定dll,在运行时调用LoadLibrary来加载dll
(这样做,可以自己决定什么时候加载、什么时候卸载DLL)
手动加载的方式:
#include#include
使用 LoadLibrary 来加载dll,
使用 FreeLibrary 来卸载dll,
它提供了一种在运行时手动加载dll的技术手段,增加了编程的灵活性
(只要有*.dll就可以,也不需要 *.lib 和 *.h )
对DLL的要求:
1、要求待调用的函数按"C"方式编译(符号名即函数名)
extern "C" __declspec(dllexprot) int MyAdd(int a, int b);
2、dll文件放在可被系统搜索到的路径
代码:
#include#include #include int main(){ wchar_t* Path = L"12_18_DLL01.dll"; HINSTANCE handle = LoadLibrary(Path); // 字符集兼容问题 if (handle) { // 定义要找的函数原型 typedef int(*DLL_FUNCTION_ADD) (int, int); // 找到目标函数的地址 注意实现必须用 extern "C" 的方式编译 DLL_FUNCTION_ADD dll_func = (DLL_FUNCTION_ADD)GetProcAddress( handle, "Add"); if (dll_func) { // 调用该函数 int result = dll_func(1, 2); std::cout << result << std::endl; } } FreeLibrary(handle); return 0;}
八、项目的静态编译
有一种场景,将以往VS生成的 *.exe 程序拷贝到其他 没有VS运行环境的机器上,这时候程序往往是无法运行的,这是因为,VC编译默认采用动态编译的方式,因此 要么给这个机器也安装一套运行环境,要么采用静态编译的方式生成可执行文件(*.exe)
静态编译后的程序,将会包含一切程序运行时所依赖的环境
对VC来说:
静态编译:
/MT /MTd
动态编译:
/MD /MDd
动态编译和静态编译的比较:
1、动态编译不方便发布,但生成的可执行文件体积较小
2、静态编译方便发布,但生成的可执行文件体积较大