c++进阶4:动态库与静态库_函数
作用分离编译(防止拷贝源码,用二进制文件,直接调用)代码重用分类分类作用后缀静态库一个或多个.o目标文件归档在一个文件中+头文件.a共享库没有main函数的可执行文件.so动态加载库没有main函数的可执行文件,接口复合API.so0.前提(文件内容)1.准备:将原来的array.h,array.cpp拆分成两个文件;array.h, alg.h,array.cpp, alg.cpparray.h
- 作用
- 分离编译(防止拷贝源码,用二进制文件,直接调用)
- 代码重用
- 分类
分类 | 作用 | 后缀 |
---|---|---|
静态库 | 一个或多个.o目标文件归档在一个文件中+头文件 | .a |
共享库 | 没有main函数的可执行文件 | .so |
动态加载库 | 没有main函数的可执行文件,接口复合API | .so |
0.前提(文件内容)
1.准备:将原来的array.h,array.cpp拆分成两个文件;
array.h, alg.h,array.cpp, alg.cpp
- array.h
#ifndef __ARRAY_H
#define __ARRAY_H
#ifdef __cplusplus
extern "C"{
#endif // __cplusplus
void* seq_create();
void seq_destroy(void* seq);
void seq_append(void* seq,int val);
void seq_prepend(void* seq,int val);
int seq_size(void* seq);
int seq_get(void* seq,int index);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // __ARRAY_H
- alg.h
#ifndef __ALG_H
#define __ALG_H
#ifdef __cplusplus
extern "C"{
#endif // __cplusplus
// C语言不认识extern "C"
int sum(int* arr,int n);
void reverse(int* arr,int n);
int max_element(int* arr,int n);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // __ALG_H
- array.cpp
//#include "vector.h"
#include <vector>
#include "array.h"
using namespace std;
//using namespace miniSTL;
void* seq_create(){
return new vector<int>();
}
void seq_destroy(void* seq){
delete reinterpret_cast<vector<int>*>(seq);
}
void seq_append(void* seq,int val){
reinterpret_cast<vector<int>*>(seq)->push_back(val);
}
void seq_prepend(void* seq,int val){
vector<int>* p = reinterpret_cast<vector<int>*>(seq);
p->insert(p->begin(),val);
}
int seq_size(void* seq){
return reinterpret_cast<vector<int>*>(seq)->size();
}
int seq_get(void* seq,int index){
return reinterpret_cast<vector<int>*>(seq)->at(index);
}
- alg.cpp
#include "alg.h"
#include <algorithm>
#include <numeric>
using namespace std;
int sum(int* arr,int n){
return accumulate(arr,arr+n,0);
}
void reverse(int* arr,int n){
reverse(arr,arr+n);
}
int max_element(int* arr,int n){
return max_element(arr,arr+n) - arr;
}
- array_test.cpp
#include "array.h"
#include "alg.h"
#include <iostream> // 在CPP不使用io
#include <algorithm>
using namespace std;
int main(){
int arr[] = {1,3,6,2,7};
cout << sum(arr,5) << endl;
for_each(arr,arr+5,[](int n){cout << n << " ";});
cout << endl;
reverse(arr,5);
for_each(arr,arr+5,[](int n){cout << n << " ";});
cout << endl;
cout << max_element(arr,5) << endl;
}
- makefile
- 源变量的定义;结果变量的定义
OBJS = array.o alg.o array_test.o
DEST = array_test
-
假象目标
.PHONY: all clean -
结果的编译的过程
all:$(DEST)
3.1 结果的依赖性
$(DEST):$(OBJS)
3.2 gcc 运算符,结果的生成
@echo 创建测试程序$(DEST)
$(CXX) $(CXXFLAGS) $^ -o $@ ##2.2gcc 运算符,结果的生成
- 依赖源的编译的过程
##3.1源的依赖性
##3.2gcc运算符,源的生成;
$(OBJS):%.o:%.cpp
@echo 编译$(OBJS) ##3.1源的依赖性
$(CXX) $(CXXFLAGS) $< -c -o $@ ##3.2gcc运算符,源的生成;
- 清空
clean:
@echo 清空
rm -f $(DEST) $(OBJS)
- 完整案例
#1. 源变量的定义;结果变量的定义
OBJS = array.o alg.o array_test.o
DEST = array_test
.PHONY: all clean
all:$(DEST)
#2 . 结果的编译的过程
$(DEST):$(OBJS) ##2.1结果的依赖性
@echo 创建测试程序$(DEST)
$(CXX) $(CXXFLAGS) $^ -o $@ -std=c++11 ##2.2gcc 运算符,结果的生成
#3 .依赖源的编译的过程
$(OBJS):%.o:%.cpp
@echo 编译$(OBJS) ##3.1源的依赖性
$(CXX) $(CXXFLAGS) $< -c -o $@ -std=c++11 ##3.2gcc运算符,源的生成;
clean:
@echo 清空
rm -f $(DEST) $(OBJS)
1. 静态库的制作与使用
1.1 创建
- 编译源文件,生成可执行文件
注意:-c(排除main函数的影响,最好用),
-o的用法,前面是结果(.o),后面是源文件(.cpp)
g++ -c -o array.o array.cpp
g++ -c -o alg.o alg.cpp
- 生成静态库,并打包
注意:关于main,测试函数的内容不打包;
- | 参数 | 含义 |
---|---|---|
1 | ar | 打包 |
2 | r | 替换模块(replace) |
- | c | 创建库(create) |
- | s | 建立索引 |
3 | libarrayalg.a | 库名:前面必须有lib,后面必须有.a |
4 | alg.o array.o | 打包的可执行程序,不包括main |
ar -rcs libarrayalg.a alg.o array.o
tar和ar都是归档工具
tar用于创建.tar归档文件。
ar用于创建归档文件,并且为归档的目标文件中的符号建立索引。
- 查看目标文件的符号(symbol)信息
nm 目标文件
nm libarrayalg.a
nm array.o
nm alg.o
目标文件可以是.o、.a,也可以是可执行文件。
- 链接静态库
方案1:带编译的结果的名字;注意:(1)不带前后缀(2)-L -l写在后面; (3)-L当前路径的.不能丢
g++ -o array_test array_test.o -L. -liarrayalg #方案1:带编译的结果的名字;注意:(1)不带前后缀(2)-L -l写在后面; (3)-L当前路径的.不能丢
方案2:带编译的结果的名字;
g++ array_test.o -L. -liarrayalg #方案2:带编译的结果的名字;
方案3:直接写路径
g++ array_test.o ./libarrayalg.a #方案3:直接写路径
注意:库一定要放在命令行的末尾
- 测试
./a.out
./array_test
2. 共享库的制作
2.1 创建
- 编译目标文件
g++ -c -fPIC test.cpp -o test.o
- 生成动态库
g++ -shared test.o -o libtest.so
以上两步可以合并为g++ -shared -fPIC -o libtest.so test.cpp
- 选项说明
命令选项 | 作用 |
---|---|
shared | 创建动态库 |
fPIC | 代码都是与位置无关的 |
每个共享函数库都有个特殊的名字,称作soname。soname名字命名必须以lib作为前缀,然后是函数库的名字,然后是.so,最后是版本号信息。不过有个特例,就是非常底层的C库函数都不是以lib开头这样命名的。
2.2 使用
- 生成可执行文件
g++ -o main main.cpp -L. -ltest
或者
g++ -o main main.cpp ./libtest.so
注意:库一定要放在命令行的末尾
- 测试
指定动态链接库位置
export LD_LIBRARY_PATH=动态链接库位置
export LD_LIBRARY_PATH=.
执行
./main
结果
Func(100)
关于动态链接库的安装路径
- 如果安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。
- 如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下:
编辑/etc/ld.so.conf文件,加入库文件所在目录的路径
运行ldconfig ,该命令会重建/etc/ld.so.cache文件
当静态库和动态库同名时, gcc命令将优先使用动态库。
- 查看执行文件链接的动态链接库
ldd 可执行文件
也可以查看动态链接库所链接的其它动态库。
3. 动态加载库
topic:前面两种都属于静态链接库,需要重启更新;第三种属于动态连接,需要手动处理,需要的时候加载,不需要的时候,可以不用
3.1 初级版本
3.1.1 创建
- 完整代码
- 修改array.h
#ifndef __ARRAY_H
#define __ARRAY_H
#ifdef __cplusplus
extern "C"{
#endif // __cplusplus
void* seq_create();
void seq_destroy(void* seq);
void seq_append(void* seq,int val);
void seq_prepend(void* seq,int val);
int seq_size(void* seq);
int seq_get(void* seq,int index);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // __ARRAY_H
- extern关键字:在一个项目中必须保证函数、变量、枚举等在所有的源文件中保持一致,除非你指定定义为局部的。
extern "C"指令中的C,表示的一种编译和连接规约,而不是一种语言。C表示符合C语言的编译和连接规约的任何语言
extern "C"的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern “C”,表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。 - __cplusplus的值是为了表示C++的版本,目前不应该依赖该宏的值。
- 修改array_test.cpp
2.0. 头文件
#include <dlfcn.h> //0.头文件
2.1 采用句柄的手段(智能指针,函数指针的用法)
void* so_handle = dlopen("./libarralg.so",RTLD_LAZY);
if(NULL == so_handle){
cerr << "open dll error" << endl;
return 1;
}
dlclose(so_handle);
so_handle = NULL;
2.2 采用句柄的手段(智能指针,函数指针的用法)+强制类型转化
int (*sum)(int*, int) = (int(*)(int*,int))dlsym(so_handle,"sum");
if(NULL == so_handle){
cerr << "sum load error" << endl;
return 1;}
- 完整代码
#include "array.h"
//#include "alg.h"
#include <iostream> // 在CPP不使用io
#include <algorithm>
#include <dlfcn.h> //0.头文件
using namespace std;
int main(){
// 1.手动加载动态库采用句柄的手段(智能指针,函数指针的用法)
void* so_handle = dlopen("./libarralg.so",RTLD_LAZY);
if(NULL == so_handle){
cerr << "open dll error" << endl;
return 1;
}
// 2.手动加载动态库采用的函数;
int (*sum)(int*, int) = (int(*)(int*,int))dlsym(so_handle,"_Z3sumPii");
if(NULL == so_handle){
cerr << "sum load error" << endl;
return 1;}
void (*reverse)(int*, int) = (void(*)(int*,int))dlsym(so_handle,"_Z7reversePii");
if(NULL == so_handle){
cerr << "reverse load error" << endl;
return 1;}
int (*max_element)(int*, int) = (int(*)(int*,int))dlsym(so_handle,"_Z11max_elementPii");
if(NULL == so_handle){
cerr << "max_element load error" << endl;
return 1;}
auto sum = LoadFunc<ifunc_t>(so_handle,"_Z3sumPii");
auto reverse = LoadFunc<vfunc_t>(so_handle,"_Z7reversePii");
auto max_element = LoadFunc<ifunc_t>(so_handle,"_Z11max_elementPii");
int arr[] = {1,3,6,2,7};
cout << sum(arr,5) << endl;
for_each(arr,arr+5,[](int n){cout << n << " ";});
cout << endl;
reverse(arr,5);
for_each(arr,arr+5,[](int n){cout << n << " ";});
cout << endl;
cout << max_element(arr,5) << endl;
dlclose(so_handle);
so_handle = NULL;
}
3.1.2 两种情况
3.1.2.1 (命令行)
g++ -fPIC array.cpp -c
g++ -shared array.o -o libarrayalg.so
g++ array_test.cpp -ldl
./a.out
3.1.2.2 (makefile)
- 重新编译dynamic_makefile
3.1 创建动态库的时候加参数
$(CXX) -shared -fPIC -o $(LIB) $^
3.2 编译的时候程序进行修改(-ldl)
$(CXX) $(CXXFLAGS) $< -o $@ -ldl
- 完整代码 dynamic_makefile
OBJS = array.o alg.o
LIB = libarralg.so
DEST = array_test
CXXFLAGS = -g
#CXXFLAGS = -g
.PHONY: all clean
all:$(DEST)
$(DEST):array_test.cpp $(LIB)
@echo 创建测试程序$(DEST)
$(CXX) $(CXXFLAGS) $< -o $@ -ldl
$(LIB):$(OBJS)
@echo 创建动态库$(LIB)
$(CXX) -shared -fPIC -o $(LIB) $^
$(OBJS):%.o:%.cpp
@echo 编译$(OBJS)
$(CXX) $(CXXFLAGS) $< -fPIC -c -o $@
clean:
@echo 清空
rm -f $(DEST) array_test.o $(OBJS) $(LIB)
然后编译 make all
make clean
3.1.3 使用
- 执行
.\array_test
3.2 进阶版本
对array_test.cpp进行修改
- 模板的使用
template<typename F>
F LoadFunc(void* handle,const char* name){
F func = reinterpret_cast<F>(dlsym(handle,name));
if(NULL == func){
cerr << name << " is not find" << endl;
}
return func;
}
合并的写法
typedef int(* ifunc_t)(int*,int);
typedef void(* vfunc_t)(int*,int);
auto sum = LoadFunc<ifunc_t>(so_handle,"_Z3sumPii");
auto reverse = LoadFunc<vfunc_t>(so_handle,"_Z7reversePii");
auto max_element = LoadFunc<ifunc_t>(so_handle,"_Z11max_elementPii");
- 完整代码
#include "array.h"
//#include "alg.h"
#include <iostream> // 在CPP不使用io
#include <algorithm>
#include <dlfcn.h>
using namespace std;
template<typename F>
F LoadFunc(void* handle,const char* name){
F func = reinterpret_cast<F>(dlsym(handle,name));
if(NULL == func){
cerr << name << " is not find" << endl;
}
return func;
}
int main(){
// 手动加载动态库
void* so_handle = dlopen("./libarralg.so",RTLD_LAZY);
if(NULL == so_handle){
cerr << "open dll error" << endl;
return 1;
}
typedef int(* ifunc_t)(int*,int);
typedef void(* vfunc_t)(int*,int);
auto sum = LoadFunc<ifunc_t>(so_handle,"_Z3sumPii");
auto reverse = LoadFunc<vfunc_t>(so_handle,"_Z7reversePii");
auto max_element = LoadFunc<ifunc_t>(so_handle,"_Z11max_elementPii");
/*
ifunc_t sum = (ifunc_t)dlsym(so_handle,"sum");
if(NULL == sum){
cerr << "sum load error" << endl;
return 1;
}
vfunc_t reverse = (vfunc_t)dlsym(so_handle,"reverse");
if(NULL == reverse){
cerr << "reverse load error" << endl;
return 1;
}
ifunc_t max_element = (ifunc_t)dlsym(so_handle,"max_element");
if(NULL == max_element){
cerr << "reverse load error" << endl;
return 1;
}
*/
int arr[] = {1,3,6,2,7};
cout << sum(arr,5) << endl;
for_each(arr,arr+5,[](int n){cout << n << " ";});
cout << endl;
reverse(arr,5);
for_each(arr,arr+5,[](int n){cout << n << " ";});
cout << endl;
cout << max_element(arr,5) << endl;
dlclose(so_handle);
so_handle = NULL;
}
3.3 完整版本
- array:array_test.cpp
#include "array.h"
//#include "alg.h"
#include <iostream> // 在CPP不使用io
#include <algorithm>
#include <dlfcn.h>
using namespace std;
template<typename F>
F LoadFunc(void* handle,const char* name){
F func = reinterpret_cast<F>(dlsym(handle,name));
if(NULL == func){
cerr << name << " is not find" << endl;
}
return func;
}
int main(){
// 手动加载动态库
void* so_handle = dlopen("./libarralg.so",RTLD_LAZY);
if(NULL == so_handle){
cerr << "open dll error" << endl;
return 1;
}
typedef int(* ifunc_t)(int*,int);
typedef void(* vfunc_t)(int*,int);
auto sum = LoadFunc<ifunc_t>(so_handle,"_Z3sumPii");
auto reverse = LoadFunc<vfunc_t>(so_handle,"_Z7reversePii");
auto max_element = LoadFunc<ifunc_t>(so_handle,"_Z11max_elementPii");
/*
ifunc_t sum = (ifunc_t)dlsym(so_handle,"sum");
if(NULL == sum){
cerr << "sum load error" << endl;
return 1;
}
vfunc_t reverse = (vfunc_t)dlsym(so_handle,"reverse");
if(NULL == reverse){
cerr << "reverse load error" << endl;
return 1;
}
ifunc_t max_element = (ifunc_t)dlsym(so_handle,"max_element");
if(NULL == max_element){
cerr << "reverse load error" << endl;
return 1;
}
*/
int arr[] = {1,3,6,2,7};
cout << sum(arr,5) << endl;
for_each(arr,arr+5,[](int n){cout << n << " ";});
cout << endl;
reverse(arr,5);
for_each(arr,arr+5,[](int n){cout << n << " ";});
cout << endl;
cout << max_element(arr,5) << endl;
typedef void*(*seq_create_t)();
typedef void (*seq_destroy_t)(void*);
typedef void (*seq_append_t)(void*,int);
typedef int (*seq_size_t)(void*);
typedef int (*seq_get_t)(void*,int);
auto seq_create = LoadFunc<seq_create_t>(so_handle,"seq_create");
auto seq_destroy = LoadFunc<seq_destroy_t>(so_handle,"seq_destroy");
auto seq_append = LoadFunc<seq_append_t>(so_handle,"seq_append");
auto seq_size = LoadFunc<seq_size_t>(so_handle,"seq_size");
auto seq_get = LoadFunc<seq_get_t>(so_handle,"seq_get");
void* seq = seq_create();
for(int i=0;i<10;++i){
seq_append(seq,i);
}
for(int i=0;i<seq_size(seq);++i){
cout << seq_get(seq,i) << " ";
}
cout << endl;
seq_destroy(seq);
dlclose(so_handle);
so_handle = NULL;
}
3.4动态加载库的问题
出错 | 翻译 | 改正 |
---|---|---|
redefine xxx | 重复定义 | 不同的.cpp文件可能定义了相同的函数,需要手动删除,保留其中的一个; |
dump | 吐核 | (1)编译的时候: -g (2)函数名字写错,导致在动态库没有生成对应的库函数;可以用压淬后的文件名 |
cannot find shared | 路径的问题 | export LD_LIBRARY_PATH=. |
3.5 动态库(共享库、动态加载库)与静态库的区别
- 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。
- 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小。
3.6 动态加载与静态加载的区别
- 动态加载
- 灵活,可以在需要的时候进行加载,在不需要的时候进行卸载,这样可以不必占用内存。
- 可以在没有动态库时候发现,而不致程序报错。
- 加载程序中有条件才运行的库。
- 热更新,在不停止程序的前提下进行更新。
- 复杂一些,需要显示获得函数地址。
- 静态加载
- 简单方便
- 没有找到动态库时,系统报错
- 加载运行很久的库
4. 总结
静态库、共享库与动态库编译链接使用比较
静态库与动态库Make file比较
5. 补充
使用动态库的方法还有如下几种
- 方法一:连接前,添加动态库目录到环境变量LD_RUN_PATH。
export LD_RUN_PATH=动态库目录
-
方法二:编译链接时,添加链接选项-Wl,-rpath -Wl,动态库目录
-
方法三:执行前,添加动态库目录到环境变量LD_LIBRARY_PATH。
export LD_LIBRARY_PATH=动态库目录
- 方法四:添加共享库目录/usr/local/lib到共享库配置文件
echo 动态库目录 >> /etc/ld.so.conf
ldconfig
- 参考
更多推荐
所有评论(0)