这次分享的宗旨是让大家学会创建与使用静态库动态库, 知道静态库与动态库的区别, 知道使用的时候如何选择这里不深入介绍静态库动态库的底层格式, 内存布局等, 有兴趣的同学, 推荐一本书程序员的自我修养链接装载与库
什么是库
库是写好的现有的, 成熟的, 可以复用的代码现实中每个程序都要依赖很多基础的底层库, 不可能每个人的代码都从零开始, 因此库的存在意义非同寻常
本质上来说库是一种可执行代码的二进制形式, 可以被操作系统载入内存执行库有两种: 静态库 (.a.lib) 和动态库 (.so.dll)
所谓静态动态是指链接回顾一下, 将一个程序编译成可执行程序的步骤:
图: 编译过程
静态库
之所以成为静态库, 是因为在链接阶段, 会将汇编生成的目标文件. o 与引用到的库一起链接打包到可执行文件中因此对应的链接方式称为静态链接
试想一下, 静态库与汇编生成的目标文件一起链接为可执行文件, 那么静态库必定跟. o 文件格式相似其实一个静态库可以简单看成是一组目标文件 (.o/.obj 文件) 的集合, 即很多目标文件经过压缩打包后形成的一个文件静态库特点总结:
l 静态库对函数库的链接是放在编译时期完成的
l 程序在运行时与函数库再无瓜葛, 移植方便
l 浪费空间和资源, 因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件
下面编写一些简单的四则运算 C++ 类, 将其编译成静态库给他人用, 头文件如下所示:
- #pragma once
- class StaticMath
- {
- public:
- StaticMath(void);
- ~StaticMath(void);
- static double add(double a, double b);// 加法
- static double sub(double a, double b);// 减法
- static double mul(double a, double b);// 乘法
- static double div(double a, double b);// 除法
- void print();
- };
Linux 下使用 ar 工具 Windows 下 vs 使用 lib.exe, 将目标文件压缩到一起, 并且对其进行编号和索引, 以便于查找和检索一般创建静态库的步骤如图所示:
图: 创建静态库过程
Linux 下创建与使用静态库
Linux 静态库命名规则
Linux
静态库命名规范, 必须是
"lib[your_library_name].a"
:lib 为前缀, 中间是静态库名, 扩展名为. a
创建静态库 (.a)
通过上面的流程可以知道, Linux 创建静态库过程如下:
l
首先, 将代码文件编译成目标文件. o(StaticMath.o)
g++ -c StaticMath.cpp |
注意带参数 - c, 否则直接编译为可执行文件
l
然后, 通过 ar 工具将目标文件打包成. a 静态库文件
ar -crv libstaticmath.a StaticMath.o |
生成静态库
libstaticmath.a
大一点的项目会编写 makefile 文件 (CMake 等等工程管理工具) 来生成静态库, 输入多个命令太麻烦了
使用静态库
编写使用上面创建的静态库的测试代码:
#include "StaticMath.h" #include <iostream> using namespace std; int main(int argc, char* argv[]) { double a = 10; double b = 2; cout << "a + b =" << StaticMath::add(a, b) << endl; cout << "a - b =" << StaticMath::sub(a, b) << endl; cout << "a * b =" << StaticMath::mul(a, b) << endl; cout << "a / b =" << StaticMath::div(a, b) << endl; StaticMath sm; sm.print(); system("pause"); return 0; } |
Linux 下使用静态库, 只需要在编译的时候, 指定静态库的搜索路径 (-L 选项) 指定静态库名 (不需要 lib 前缀和. a 后缀,-l 选项)
# g++ TestStaticLibrary.cpp -L../StaticLibrary -lstaticmath
l -L: 表示要连接的库所在目录
l -l: 指定链接时需要的动态库, 编译器查找动态连接库时有隐含的命名规则, 即在给出的名字前面加上 lib, 后面加上. a 或. so 来确定库的名称
Windows 下创建与使用静态库
创建静态库 (.lib)
如果是使用 VS 命令行生成静态库, 也是分两个步骤来生成程序:
l 首先, 通过使用带编译器选项 /c 的 Cl.exe 编译代码 (cl /c StaticMath.cpp), 创建名为 StaticMath.obj 的目标文件
l
然后, 使用库管理器
Lib.exe
链接代码
(lib StaticMath.obj), 创建静态库 StaticMath.lib
当然, 我们一般不这么用, 使用 VS 工程设置更方便创建 win32 控制台程序时, 勾选静态库类型; 打开工程属性面板
è
配置属性
è 常规, 配置类型选择静态库
图: vs 静态库项目属性设置
Build 项目即可生成静态库
使用静态库
测试代码 Linux 下面的一样有 3 种使用方法:
方法一:
在 VS 中使用静态库方法:
l 工程属性面板 è 通用属性 è 框架和引用 è 添加引用, 将显示添加引用对话框 项目选项卡列出了当前解决方案中的各个项目以及可以引用的所有库
在
项目
选项卡中, 选择
StaticLibrary
单击确定
l 添加 StaticMath.h 头文件目录, 必须修改包含目录路径打开工程属性面板 è 配置属性 è C/C++è 常规
, 在
附加包含目录属性值中, 键入 StaticMath.h 头文件所在目录的路径或浏览至该目录
编译运行 OK
图: 静态库测试结果 (vs)
如果引用的静态库不是在同一解决方案下的子工程, 而是使用第三方提供的静态库 lib 和头文件, 上面的方法设置不了还有 2 中方法设置都可行
方法二:
打开工程
属性面板
è
配置属性
è
链接器
è 命令行, 输入静态库的完整路径即可
方法三:
l
属性面板
è
配置属性
è
链接器
è 常规, 附加依赖库目录中输入, 静态库所在目录;
l
属性面板
è
配置属性
è
链接器
è
输入, 附加依赖库中输入静态库名 StaticLibrary.lib
动态库
通过上面的介绍发现静态库, 容易使用和理解, 也达到了代码复用的目的, 那为什么还需要动态库呢?
为什么还需要动态库?
为什么需要动态库, 其实也是静态库的特点导致
l 空间浪费是静态库的一个问题
l 另一个问题是静态库对程序的更新部署和发布页会带来麻烦如果静态库 liba.lib 更新了, 所以使用它的应用程序都需要重新编译发布给用户 (对于玩家来说, 可能是一个很小的改动, 却导致整个程序重新下载, 全量更新)
动态库在程序编译时并不会被连接到目标代码中, 而是在程序运行是才被载入不同的应用程序如果调用相同的库, 那么在内存里只需要有一份该共享库的实例, 规避了空间浪费问题动态库在程序运行是才被载入, 也解决了静态库对程序的更新部署和发布页会带来麻烦用户只需要更新动态库即可, 增量更新
动态库特点总结:
l 动态库把对一些库函数的链接载入推迟到程序运行的时期
l 可以实现进程之间的资源共享 (因此动态库也称为共享库)
l 将一些程序升级变得简单
l 甚至可以真正做到链接载入完全由程序员在程序代码中控制 (显示调用)
Window 与 Linux 执行文件格式不同, 在创建动态库的时候有一些差异
l 在 Windows 系统下的执行文件格式是 PE 格式, 动态库需要一个 DllMain 函数做出初始化的入口, 通常在导出函数的声明时需要有_declspec(dllexport) 关键字
l Linux 下 gcc 编译的执行文件默认是 ELF 格式, 不需要初始化入口, 亦不需要函数做特别的声明, 编写比较方便
与创建静态库不同的是, 不需要打包工具 (arlib.exe), 直接使用编译器即可创建动态库
Linux 下创建与使用动态库
linux 动态库的命名规则
动态链接库的名字形式为
libxxx.so
, 前缀是 lib, 后缀名为. so
l 针对于实际库文件, 每个共享库都有个特殊的名字 soname 在程序启动后, 程序通过这个名字来告诉动态加载器该载入哪个共享库
l 在文件系统中, soname 仅是一个链接到实际动态库的链接对于动态库而言, 每个库实际上都有另一个名字给编译器来用它是一个指向实际库镜像文件的链接文件 (lib+soname+.so)
创建动态库 (.so)
编写四则运算动态库代码:
- #pragma once
- class DynamicMath
- {
- public:
- DynamicMath(void);
- ~DynamicMath(void);
- static double add(double a, double b);//?ó.¨
- static double sub(double a, double b);//??.¨
- static double mul(double a, double b);//3?.¨
- static double div(double a, double b);//3y.¨
- void print();
- };
g++ -fPIC -c DynamicMath.cpp |
l 首先, 生成目标文件, 此时要加编译器选项 - fpic
-fPIC
创建与地址无关的编译程序 (pic,position independent code), 是为了能够在多个应用程序间共享
l 然后, 生成动态库, 此时要加链接器选项 - shared
g++ -shared -o libdynmath.so DynamicMath.o |
-shared 指定生成动态链接库
其实上面两个步骤可以合并为一个命令:
g++ -fPIC -shared -o libdynmath.so DynamicMath.cpp |
使用动态库
编写使用动态库的测试代码:
- #include "../DynamicLibrary/DynamicMath.h"
- #include <iostream>
- using namespace std;
- int main(int argc, char* argv[])
- {
- double a = 10;
- double b = 2;
- cout << "a + b =" << DynamicMath::add(a, b) << endl;
- cout << "a - b =" << DynamicMath::sub(a, b) << endl;
- cout << "a * b =" << DynamicMath::mul(a, b) << endl;
- cout << "a / b =" << DynamicMath::div(a, b) << endl;
- DynamicMath dyn;
- dyn.print();
- return 0;
- }
- g++ TestDynamicLibrary.cpp -L../DynamicLibrary -ldynmath
然后运行:./a.out, 发现竟然报错了!!!
可能大家会猜测, 是因为动态库跟测试程序不是一个目录, 那我们验证下是否如此:
发现还是报错!!! 那么, 在执行的时候是如何定位共享库文件的呢?
1) 当系统加载可执行代码时候, 能够知道其所依赖的库的名字, 但是还需要知道绝对路径此时就需要系统动态载入器 (dynamic linker/loader)
2)
对于
elf
格式的可执行程序, 是由
ld-linux.so*
来完成的, 它先后搜索
elf
文件的
DT_RPATH
段环境变量
LD_LIBRARY_PATH
/etc/ld.so.cache 文件列表 / lib/,/usr/lib 目录找到库文件后将其载入内存
如何让系统能够找到它:
l
如果安装在
/lib
或者 / usr/lib 下, 那么 ld 默认能够找到, 无需其他操作
l 如果安装在其他目录, 需要将其添加到 / etc/ld.so.cache 文件中, 步骤如下:
n 编辑 / etc/ld.so.conf 文件, 加入库文件所在目录的路径
n
运行 ldconfig , 该命令会重建 / etc/ld.so.cache 文件
我们将创建的动态库复制到 / usr/lib 下面, 然后运行测试程序
Windows 下创建与使用动态库
创建动态库 (.dll)
与 Linux 相比, 在 Windows 系统下创建动态库要稍微麻烦一些首先, 需要一个 DllMain 函数做出初始化的入口 (创建 win32 控制台程序时, 勾选 DLL 类型会自动生成这个文件):
- // dllmain.cpp : Defines the entry point for the DLL application.
- #include "stdafx.h"
- BOOL APIENTRY DllMain( HMODULE hModule,
- DWORD ul_reason_for_call,
- LPVOID lpReserved
- )
- {
- switch (ul_reason_for_call)
- {
- case DLL_PROCESS_ATTACH:
- case DLL_THREAD_ATTACH:
- case DLL_THREAD_DETACH:
- case DLL_PROCESS_DETACH:
- break;
- }
- return TRUE;
- }
通常在导出函数的声明时需要有_declspec(dllexport) 关键字:
- #pragma once
- class DynamicMath
- {
- public:
- __declspec(dllexport) DynamicMath(void);
- __declspec(dllexport) ~DynamicMath(void);
- static __declspec(dllexport) double add(double a, double b);// 加法
- static __declspec(dllexport) double sub(double a, double b);// 减法
- static __declspec(dllexport) double mul(double a, double b);// 乘法
- static __declspec(dllexport) double div(double a, double b);// 除法
- __declspec(dllexport) void print();
- };
生成动态库需要设置工程属性, 打开工程
属性面板
è
配置属性
è 常规, 配置类型选择动态库
图: v 动态库项目属性设置
Build 项目即可生成动态库
使用动态库
创建 win32 控制台测试程序:
- #include "stdafx.h"
- #include "DynamicMath.h"
- #include <iostream>
- using namespace std;
- int _tmain(int argc, _TCHAR* argv[])
- {
- double a = 10;
- double b = 2;
- cout << "a + b =" << DynamicMath::add(a, b) << endl;
- cout << "a - b =" << DynamicMath::sub(a, b) << endl;
- cout << "a * b =" << DynamicMath::mul(a, b) << endl;
- cout << "a / b =" << DynamicMath::div(a, b) << endl;
- DynamicMath dyn;
- dyn.print();
- system("pause");
- return 0;
- }
l 工程属性面板 è 通用属性 è 框架和引用 è 添加引用, 将显示添加引用对话框项目选项卡列出了当前解决方案中的各个项目以及可以引用的所有库 在项目选项卡中, 选择 DynamicLibrary 单击确定方法一:
l 添加 DynamicMath.h 头文件目录, 必须修改包含目录路径打开工程属性面板 è 配置属性 è C/C++è 常规, 在附加包含目录属性值中, 键入 DynamicMath.h 头文件所在目录的路径或浏览至该目录
编译运行 OK
图: 动态库测试结果 (vs)
方法二:
l
属性面板
è
配置属性
è
链接器
è 常规, 附加依赖库目录中输入, 动态库所在目录;
l
属性面板
è
配置属性
è
链接器
è
输入, 附加依赖库中输入动态库编译出来的 DynamicLibrary.lib
这里可能大家有个疑问, 动态库怎么还有一个 DynamicLibrary.lib 文件? 即无论是静态链接库还是动态链接库, 最后都有 lib 文件, 那么两者区别是什么呢? 其实, 两个是完全不一样的东西
StaticLibrary.lib 的大小为 190KB,DynamicLibrary.lib 的大小为 3KB, 静态库对应的 lib 文件叫静态库, 动态库对应的 lib 文件叫导入库实际上静态库本身就包含了实际执行代码符号表等等, 而对于导入库而言, 其实际的执行代码位于动态库中, 导入库只包含了地址符号表等, 确保程序找到对应函数的一些基本地址信息
动态库的显式调用
上面介绍的动态库使用方法和静态库类似属于隐式调用, 编译的时候指定相应的库和查找路径其实, 动态库还可以显式调用在 C 语言中, 显示调用一个动态库轻而易举!
在 Linux 下显式调用动态库
#include <dlfcn.h>, 提供了下面几个接口:
l void * dlopen( const char * pathname, int mode ): 函数以指定模式打开指定的动态连接库文件, 并返回一个句柄给调用进程
l void* dlsym(void* handle,const char* symbol):dlsym 根据动态链接库操作句柄 (pHandle) 与符号 (symbol), 返回符号对应的地址使用这个函数不但可以获取函数地址, 也可以获取变量地址
l int dlclose (void *handle):dlclose 用于关闭指定句柄的动态链接库, 只有当此动态链接库的使用计数为 0 时, 才会真正被系统卸载
l const char *dlerror(void): 当动态链接库操作函数执行失败时, dlerror 可以返回出错信息, 返回值为 NULL 时表示操作函数执行成功
在 Windows 下显式调用动态库
应用程序必须进行函数调用以在运行时显式加载 DLL 为显式链接到 DLL, 应用程序必须:
l
调用
LoadLibrary(或相似的函数) 以加载 DLL 和获取模块句柄
l 调用 GetProcAddress, 以获取指向应用程序要调用的每个导出函数的函数指针由于应用程序是通过指针调用 DLL 的函数, 编译器不生成外部引用, 故无需与导入库链接
l
使用完
DLL 后调用 FreeLibrary
显式调用 C++ 动态库注意点
对 C++ 来说, 情况稍微复杂显式加载一个 C++ 动态库的困难一部分是因为 C++ 的 name mangling; 另一部分是因为没有提供一个合适的 API 来装载类, 在 C++ 中, 您可能要用到库中的一个类, 而这需要创建该类的一个实例, 这不容易做到
name mangling 可以通过 extern "C" 解决 C++ 有个特定的关键字用来声明采用 C binding 的函数: extern "C" 用 extern "C" 声明的函数将使用函数名作符号名, 就像 C 函数一样因此, 只有非成员函数才能被声明为 extern "C", 并且不能被重载尽管限制多多, extern "C" 函数还是非常有用, 因为它们可以象 C 函数一样被 dlopen 动态加载冠以 extern "C" 限定符后, 并不意味着函数中无法使用 C++ 代码了, 相反, 它仍然是一个完全的 C++ 函数, 可以使用任何 C++ 特性和各种类型的参数
- Class
- :http://www.cppblog.com/codejie/archive/2009/09/24/97141.html
- l
- C++ dlopen mini HOWTO
- :http://blog.csdn.net/denny_233/article/details/7255673
来源: http://www.bubuko.com/infodetail-2503593.html