Ubuntu 上只有四行命令,麻烦全在编完之后:三个同名的 .so 各自负责什么。 弄清「链接期用哪个、运行期用哪个」,三类报错就都能自己对上。
3.4.1 在 Windows 上编,这一节在 Ubuntu 上编同一个库。 两边的 CMake 是同一套,差别只在产物格式与找库方式。
一、四行命令装完
📋 Ubuntu 18.04 上的完整流程:
① 装工具:sudo apt install -y cmake g++
② 解压进目录:tar xf jrtplib-3.11.2.tar.gz && cd jrtplib-3.11.2
③ 编:mkdir build && cd build && cmake .. && make -j4
④ 装:sudo make install && sudo ldconfig
注意第 ④ 步后面那个 ldconfig 不是可选的。
make install 只负责把文件拷进 /usr/local/lib,而动态链接器的缓存不会自动刷新。
少了这一句,就会出现「编译明明过了,一运行就说找不到库」。
另外 jthread 也要按同样顺序先装一遍,和 Windows 侧的依赖关系完全一样。
二、三个同名的 .so 各管什么
装完之后 /usr/local/lib 里会多出三个名字很像的文件:
① libjrtplib.so.3.11.2:真身,带完整版本号。
② libjrtplib.so.3:软链,SONAME,运行时靠它找。
③ libjrtplib.so:软链,编译链接时靠它找。
为什么要分两级?
因为「编译期」和「运行期」要的东西不一样。
编译期你在命令行写 -ljrtplib,链接器去找 libjrtplib.so——它只需要知道「有这么一个库」。
运行期程序里记的是 SONAME,也就是 libjrtplib.so.3——只要主版本号还是 3,库升级到 3.11.3 也不用重新编译。
这个设计的价值在运维上: 小版本升级只换真身文件、不动软链,所有已编好的程序立刻生效。 主版本号变了才需要重编,因为那意味着接口不兼容。
三、链接参数与三类报错
编译命令是 g++ main.cpp -ljrtplib -ljthread -o demo。
注意 -l 后面要砍掉 lib 前缀和 .so 后缀,写中间的裸名。
三类报错,对应三个不同的阶段,很容易混:
① 编译期找不到头文件:说明 include 路径不对,加 -I/usr/local/include。
② 链接期 undefined reference:十有八九是只链了 jrtplib、忘了 jthread,把 -ljthread 补上。
③ 运行期找不到 .so:说明动态链接器搜不到,sudo ldconfig 或设 LD_LIBRARY_PATH。
怎么快速区分是②还是③?
看报错发生在 g++ 还是 ./demo。
前者是链接问题,后者是运行问题,两者完全不是一回事。
很多新手把「运行时找不到库」当成「装错了」,然后反复重装,白折腾。
四、Windows 与 Ubuntu 的三处差别
编同一个库,两边真正不同的只有三处:
① 构建工具的输出不同:Windows 是 CMake 生成 .sln 交给 VS;Ubuntu 是 CMake 生成 Makefile 交给 make。CMake 本身是同一个。
② 产物后缀不同:Windows 是 .lib + .dll,Ubuntu 是 .a + .so。
.lib 对应 .a(导入库),.dll 对应 .so(动态库)。
③ 找库的方式不同:Windows 按 PATH 与「exe 同目录」;Ubuntu 按 ld.so.cache 与 LD_LIBRARY_PATH。
所以有一个经验可以直接搬:
Windows 上「把 dll 拷到 exe 旁边」这一招,在 Ubuntu 上要换成「把 .so 拷到某个目录再设 LD_LIBRARY_PATH」。
思路一样,动的地方不同。
还有一条隐蔽的差别:Ubuntu 上默认开 -fPIC 与 -O2,Windows 的 Debug 默认是 -Od 且带调试信息。
所以同一个工程在两边「表现不一样」时,先看编译选项,别急着怀疑代码。
五、动手 Demo:Ubuntu 侧的编译步骤与三个软链
这是【📋 实操路径】——需要一台 Ubuntu 18.04。 沙箱里没有 Linux,所以这一段是可直接照抄的命令清单。
# ① 装工具链
sudo apt install -y cmake g++
# ② 编 jthread(必须先编)
tar xf jthread-1.3.3.tar.gz && cd jthread-1.3.3
mkdir build && cd build && cmake .. && make -j4
sudo make install
# ③ 编 jrtplib
cd ~ && tar xf jrtplib-3.11.2.tar.gz && cd jrtplib-3.11.2
mkdir build && cd build && cmake .. && make -j4
sudo make install
# ④ 刷新动态库缓存——这一步不能省
sudo ldconfig
# ⑤ 看一眼三个同名文件
ls -l /usr/local/lib/libjrtplib*
# ⑥ 编自己的程序,两个库都要链
g++ main.cpp -ljrtplib -ljthread -o demo
./demo
实跑输出:
--------------------------------------------------------------------
Ubuntu 18 编译 JRTPLIB——库文件名与链接参数
--------------------------------------------------------------------
📋 **Ubuntu 侧的步骤:**
**① 装工具**:`sudo apt install cmake g++`
**② 进目录**:`mkdir build && cd build`
**③ 编**:`cmake .. && make -j4`
**④ 装**:`sudo make install`
**① 编完之后系统里多了什么**
文件 在哪 作用
---------------------------------------------------------------------------
libjrtplib.so.3.11.2 /usr/local/lib 真身,带版本号
libjrtplib.so.3 /usr/local/lib SONAME 软链,运行时找它
libjrtplib.so /usr/local/lib 编译期软链,链接时找它
rtpsession.h 等 /usr/local/include 头文件
**三个同名的 .so 是有讲究的。**
**链接时用不带版本的那个**(`-ljrtplib` 找 `libjrtplib.so`)。
**运行时用 SONAME 那个**(`libjrtplib.so.3`)。
**这样程序编好之后,库升级成 3.11.3 也不用重新编译**——只要 SONAME 还是 3。
**② 链接参数与找不到库的三个原因**
**编译命令:`g++ main.cpp -ljrtplib -ljthread -o demo`。**
**注意 `-l` 要去掉 `lib` 前缀和 `.so` 后缀。**
现象 真正的原因 怎么办
--------------------------------------------------------------------------------
编译期找不到头文件 没装或装在别处 加 `-I` 指到头文件目录
链接期 undefined reference 少了 `-ljthread` 两个库都要链
运行时找不到 .so 不在 ld 的搜索路径 `ldconfig` 一下
**「编译过了运行报找不到库」是最常见的一条。**
**原因是 `make install` 把库放进了 `/usr/local/lib`,而有些发行版的 `ld.so.conf` 默认不含它。**
**`sudo ldconfig` 一句就解决。**
**③ Ubuntu 与 Windows 的差别,其实只有三处**
**① 构建工具**:Windows 用 CMake 生成 .sln 再用 VS 编;
**Ubuntu 用 CMake 生成 Makefile 再用 make 编。CMake 是同一个。**
**② 产物**:Windows 是 `.lib + .dll`,Ubuntu 是 `.a + .so`。
**`.lib` 对应 `.a`(导入库),`.dll` 对应 `.so`(动态库)。**
**③ 找库的方式**:Windows 靠 PATH 与同目录,**Ubuntu 靠 `ld.so.cache` 与 `LD_LIBRARY_PATH`。**
**所以 Windows 上「把 dll 拷到 exe 旁边」这招,在 Ubuntu 上要换成「设环境变量」。**
ls -l 会看到三个条目,两个是箭头指向真身的软链——这就是编译期与运行期分开的两个入口。
ldconfig 放在 make install 之后,它扫的是 /usr/local/lib 这类标准目录,把 SONAME 登记进缓存。
最后一行 ./demo 才是验收集:能跑起来说明头文件、链接、运行库三关全过。
30 秒实验
- 故意删掉
sudo ldconfig再跑程序,看报错长什么样——error while loading shared libraries: libjrtplib.so.3,记下这个长相,以后一眼能认。 - 把
libjrtplib.so.3这个软链改名,再跑一次——证明运行期认的是 SONAME,不是不带版本的那个。 - 只写
-ljrtplib不写-ljthread,看链接报错提到的符号名——里面会出现 jthread 的类名,这就是依赖关系的直接证据。
常见误区
- 「make install 完就能用」——还要
ldconfig,否则运行期找不到库。 - 「三个 .so 是重复文件,删两个省空间」——删了就没法编、或者没法运行,两个软链各管一头。
- 「运行时报找不到库 = 没装好」——十有八九只是缓存没刷新,一句
ldconfig的事,不用重装。
自测三问(点开对答案)
1. 为什么 `make install` 之后还要 `ldconfig`? —— **把新的 SONAME 登记进动态链接器缓存**,否则运行期找不到库。 2. 链接期和运行期分别用哪个软链? —— **链接期用 `libjrtplib.so`,运行期用 `libjrtplib.so.3`(SONAME)。** 3. `.lib` 与 `.a`、`.dll` 与 `.so` 是怎么对应的? —— **`.lib` 对 `.a`(导入库),`.dll` 对 `.so`(动态库)。**功能位置一致。- ☐ (本节完,下一节 3.4.3 使用VS2015搭建JRTPLIB开发环境并收发包案例)