C/C++ linux C/C++静态库与动态库详解(附类go语言gin框架——wfrest库的推荐与链接使用)

笔者在接触过go语言的gin框架后,就被如此简单的使用方式给震惊了,想到c++的网络编程,过于繁杂,就想找找有没有类 gin 的网络库 ,于是便找到了c++11异步restful网络框架wfrest。

#include "wfrest/HttpServer.h"
using namespace wfrest;

int main()
{
    HttpServer svr;

    // 发送数据
    svr.GET("/hello", [](const HttpReq *req, HttpResp *resp)
    {
        resp->String("world\n");
    });

    // 获取http 请求的数据
    svr.POST("/post", [](const HttpReq *req, HttpResp *resp)
    {
        std::string& body = req->body();
        fprintf(stderr, "post data : %s\n", body.c_str());
    });

    // 文件读写都是异步
    // 发送文件
    svr.GET("/file", [](const HttpReq *req, HttpResp *resp)
    {
        resp->File("todo.txt");
    });

    // 上传文件
    svr.POST("/upload", [](const HttpReq *req, HttpResp *resp)
    {
        std::string& body = req->body();   
        resp->Save("test.txt", std::move(body));
    });

    // 发送json数据
    svr.GET("/json", [](const HttpReq *req, HttpResp *resp)
    {
        std::string json = R"(
        {
            "numbers": [1, 2, 3]
        }
        )";
        resp->Json(json);
    });

    if (svr.start(8888) == 0)
    {
        getchar()
        svr.stop();
    } else
    {
        fprintf(stderr, "Cannot start server");
        exit(1);
    }
    return 0;
}

这里是作者对此框架的介绍

有哪些值得学习的国内 c++ 开源项目? - Chanchan的回答 - 知乎

github仓库地址
可是对于小白来说,C++ 库不像go语言那样直接import,我也是搜罗了好久才勉强弄懂一点

下面是些前置知识

什么是库

库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。

本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)动态库(.so、.dll)

回顾一下,将一个程序编译成可执行程序的步骤:

GCC编译器基本参数

  1. 预处理-Pre-Processing //.i文件
# -E 选项指示编译器仅对输入文件进行预处理
g++ -E test.cpp -o test.i //.i文件
  1. 编译-Compiling // .s文件
# -S 编译选项告诉 g++ 在为 C++ 代码产生了汇编语言文件后停止编译
# g++ 产生的汇编语言文件的缺省扩展名是 .s
g++ -S test.i -o test.s
  1. 汇编-Assembling // .o文件
# -c 选项告诉 g++ 仅把源代码编译为机器语言的目标代码
# 缺省时 g++ 建立的目标代码文件有一个 .o 的扩展名。
g++ -c test.s -o test.o
  1. 链接-Linking // bin文件
# -o 编译选项来为将产生的可执行文件用指定的文件名
g++ test.o -o test

其中库的生成就是拿汇编成的 .o目标文件进行有别于生成可执行文件的链接(通过 ar 命令)。

ar命令

  • 用途说明

    创建静态库 .a文件。

  • 格式:

    ar rcs  libxxx.a xx1.o xx2.o   //记住这个就行了,静态库创建最常用
    
  • 常用参数

    • 参数r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置.

    • 参数c:创建一个库。不管库是否存在,都将创建。

    • 参数s:创建目标文件索引,这在创建较大的库时能加快时间。

    • 格式:

      ar t libxxx.a   //显示库文件中有哪些目标文件,只显示名称。
      
      ar tv libxxx.a  //显示库文件中有哪些目标文件,显示文件名、时间、大小等详细信息。
      
      nm -s libxxx.a  //显示库文件中的索引表。
      
      ranlib libxxx.a //为库文件创建索引表。
      

      其中创建静态库一般只有 rcs 参数

为什么需要库(library)

  • 有时候我们会有多个可执行文件,他们之间用到的某些功能是相同的,我们想把这些共用的功能做成一个,方便大家一起共享。
  • 库中的函数可以被可执行文件调用,也可以被其他库文件调用。
  • 库文件又分为静态库文件动态库文件
  • 其中静态库相当于直接把代码插入到生成的可执行文件中,会导致体积变大,但是只需要一个文件即可运行。
  • 而动态库则只在生成的可执行文件中调用库函数的位置填上相应的符号名其实也是个地址(类似于汇编中的call 符号名),当可执行文件被加载时会读取指定目录中的 .dll (Windows动态库文件后缀名)文件或 .so (linux动态库文件后缀名),加载到内存中空闲的位置,并且替换相应的“符号名”指向的地址为加载后的地址,这个过程称为重定向。这样以后函数被调用就会跳转到动态加载的地址去。
  • 库文件搜索路径:
    • Windows:可执行文件同目录,其次是环境变量%PATH%
    • Linux:ELF格式可执行文件的RPATH,其次是/usr/lib等

静态库

  • 命名规则
    • Linux
      • libxxx.a
        • lib: 前缀
        • .a: 后缀
        • xxx: 名字, 由库的制作者定的
    • windows
      • libxx.lib

静态库的创建

wakk@wakk-virtual-machine~/文档$ tree .
.
├── add.c
├── div.c
├── include
│   └── head.h
├── main.c
├── mult.c
└── sub.c

1 directory, 6 files

# 1. 将源文件生成 .o 文件
$ gcc -c sub.c add.c mult.c div.c  -I ./include/
wakk@wakk-virtual-machine~/文档$ tree .
.
├── add.c
├── add.o
├── div.c
├── div.o
├── include
│   └── head.h
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o

1 directory, 10 files

# 2. 将.o 文件打包 生成 库文件
# 语法: ar rcs 生成的库的名字 *.o文件

$ ar rcs libcalc.a *.o
wakk@wakk-virtual-machine~/文档$ ls
add.c  add.o  div.c  div.o  include  `libcalc.a`  main.c  mult.c  mult.o  sub.c  sub.o

# 发布`libcalc.a`  `head.h` 拷贝给使用者即可

静态库使用

.
├── include
│   └── head.h	`静态库对应的头文件`
├── libcalc.a	`静态库`
└── main.c	`测试程序`

1 directory, 3 files

# 编译测试程序
$ gcc main.c -o app -I include/
/home/文档: In function `main':
main.c:(.text+0x38): undefined reference to `add'
main.c:(.text+0x58): undefined reference to `subtract'
main.c:(.text+0x78): undefined reference to `multiply'
main.c:(.text+0x98): undefined reference to `divide'
collect2: error: ld returned 1 exit status

# 错误原因: 编译的时候找不到对应的库,只有声明(在头文件里), 库中有函数定义(函数实现)
# 指定自己编译的库需要使用的参数:
	-L: 指定库的路径
	-l: 指定库的名字(掐头(lib) 去尾(.a))
$ gcc main.c -o app -I include/ -L ./ -l calc

动态库/共享库

  • 命名规则
    • linux:
      • libxxx.so
        • 前缀: lib
        • 后缀: .so
        • 库的名字: xxx, 制作库的人指定的
    • windows:
      • vs版
        • libxxx.lib
        • libxxx.dll
      • 非vs版
        • libxx.dll

动态库的创建

动态库的创建要+上-fPIC -shared参数,

  • -shared 选项用于生成动态链接库;
  • -fpic(还可写成 -fPIC)选项的功能是,令 GCC 编译器生成动态链接库(多个目标文件的压缩包)时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。这样,无论将来链接库被加载到内存的什么位置,都可以正常使用。

具体为什么要加参考下面帖子

gcc 编译参数 -fPIC 作用

.
├── add.c
├── div.c
├── include
│   └── head.h
├── main.c
├── mult.c
└── sub.c

1 directory, 6 files
# 制作步骤:
# 1. 将源文件生成 .o 文件 -fpic/fPIC
$ gcc -c add.c div.c mult.c sub.c -fpic -I ./include/
# 2. 将 .o 文件 打包(gcc -shared)成 库文件 .so
$ gcc -shared *.o -o libcalc.so
wakk@wakk-virtual-machine~/文档 $ tree .
.
├── add.c
├── add.o
├── div.c
├── div.o
├── include
│   └── head.h
├── `libcalc.so` # 生成的动态库
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o

# 发布`libcalc.so``head.h` 发布给使用者

动态库使用

.
├── include
│   └── head.h
├── libcalc.so
└── main.c

$ gcc -I include/ main.c -L./ -lcalc -o app
$ ./app
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

# 使用命令检测可执行程序能不能加载到对应的动态库?
ldd 可执行程序名

$ ldd app
        linux-vdso.so.1 =>  (0x00007ffde8d77000)
        libhello.so => `not found`
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f289bd43000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f289c10d000)

原因及解决

库的工作原理
  • 静态库

    • gcc main.c -o app -I include/ -L ./ -l calc
      
    • 在最终的链接阶段, 链接器会将静态库 calc 打包到可执行程序 app中

    • 在可执行程序执行的时候, app中所有的代码会被加载到内存

  • 动态库:

    • gcc -I include/ main.c -L./ -lcalc -o app
      
    • 在最终的链接阶段, 链接器不会将动态库 calc 打包到可执行程序 app中,
      在上边的命令中只是检测动态库是否存在

    • 在可执行程序执行的时候, app中所有的代码会被加载到内存, 不包括动态库代码

    • 当库中的函数被调用的时候, 动态库被加载到内存(加载的时候需要知道动态库在什么位置)

    • 库的加载是由动态链接器加载的

所以上面出现的问题是动态链接器没有找到对应的动态库

  • 动态链接器是如何加载动态库的?

它先后搜索可执行程序文件的 DT_RPATH段 —> 环境变量LD_LIBRARY_PATH —> /etc/ld.so.cache文件列表 —> /lib/, /usr/lib目录找到库文件后将其载入内存。

解决
#1. 将动态库的路径放到环境变量 LD_LIBRARY_PATH 中
在终端执行下边的命令: (这是临时设置, 当前终端被关闭或切换到其他终端该设置就无效了)
	export LD_LIBRARY_PATH=/home/文档:$LD_LIBRARY_PATH
永久设置: 将上边的命令写入到配置文件中
	- 用户级别:
		`~/.bashrc`
	- 系统级别:
		`/etc/profile`
	配置完成之后, 需要让配置文件重新被加载
		. source ~/.bashrc
		. source /etc/profile
#2. 将动态库的路径更新配置文件 /etc/ld.so.cache 中
	- 将动态库的路径添加到配置文件 /etc/ld.so.conf
	- 将/etc/ld.so.conf中的信息更新/etc/ld.so.cache 
		- sudo ldconf
#3. 将动态库添加到对应的系统目录中
	- /lib
	- /usr/lib

这里采用export LD_LIBRARY_PATH=/home/文档:$LD_LIBRARY_PATH 暂时添加

然后便成功了

wfrest

wfrest 库的获取

就按照 GitHub 上的步骤一步一步来就好了,因为wfrest 是基于 sougou的workflow的,所以–recursive 的时候会去下载workflow ,但是者在获取的时候出现点问题,就是会在获取workflow时有点慢,所以我选择不加–recursive,之后再

cd wfrest
git clone https://github.com/sogou/workflow.git

之后接着按步骤来

一般来说在 sudo make install 后便可在

/usr/local/lib

里发现对应的静态库和动态库文件了

采用动态库

这里的demo.cpp就是github主页那个Quick start代码

.
└── demo.cpp

0 directories, 1 file

此时如果直接执行

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib   //这里记得改成自己库文件所在目录
g++ demo.cpp -lwfrest -o app

会报

image-20220526115205597

这是因为workflow库还没安装

到wfrest目录下再执行

cd workflow
make
sudo make install

此时 /usr/local/lib 中可以看见了

image-20220526115750701

此时便是开始执行了

image-20220526120236510

成功了

凡是报下面错的

./app: error while loading shared libraries: libworkflow.so: cannot open shared object file: No such file or directory

先检查一下路径

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib 加了没

采用静态库

不会,希望有大佬教教我

.
├── demo.cpp
├── libwfrest.a
└── libworkflow.a

0 directories, 3 files

g++ -static demo.cpp -L ./ -lwfrest -lworkflow -lpthread -I/usr/local/include -o app //最后报错的代码

动态库能用就行(手动狗头)

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值